[
  {
    "path": ".clang-format",
    "content": "---\nAccessModifierOffset: -1\nAlignAfterOpenBracket: AlwaysBreak\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignEscapedNewlinesLeft: true\nAlignOperands:   false\nAlignTrailingComments: false\nAllowAllParametersOfDeclarationOnNextLine: false\nAllowShortBlocksOnASingleLine: false\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: Empty\nAllowShortIfStatementsOnASingleLine: false\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: true\nAlwaysBreakTemplateDeclarations: true\nBinPackArguments: false\nBinPackParameters: false\nBraceWrapping:\n  AfterClass:      false\n  AfterControlStatement: false\n  AfterEnum:       false\n  AfterFunction:   false\n  AfterNamespace:  false\n  AfterObjCDeclaration: false\n  AfterStruct:     false\n  AfterUnion:      false\n  BeforeCatch:     false\n  BeforeElse:      false\n  IndentBraces:    false\nBreakBeforeBinaryOperators: None\nBreakBeforeBraces: Attach\nBreakBeforeTernaryOperators: true\nBreakConstructorInitializersBeforeComma: false\nBreakAfterJavaFieldAnnotations: false\nBreakStringLiterals: false\nColumnLimit:     80\nCommentPragmas:  '^ IWYU pragma:'\n#CompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: true\nConstructorInitializerIndentWidth: 4\nContinuationIndentWidth: 4\nCpp11BracedListStyle: true\nDerivePointerAlignment: false\nDisableFormat:   false\nForEachMacros:   [ FOR_EACH_RANGE, FOR_EACH, ]\nIncludeCategories:\n  - Regex:           '^<.*\\.h(pp)?>'\n    Priority:        1\n  - Regex:           '^<.*'\n    Priority:        2\n  - Regex:           '.*'\n    Priority:        3\nIndentCaseLabels: true\nIndentWidth:     2\nIndentWrappedFunctionNames: false\nKeepEmptyLinesAtTheStartOfBlocks: false\nMacroBlockBegin: ''\nMacroBlockEnd:   ''\nMaxEmptyLinesToKeep: 1\nNamespaceIndentation: None\nObjCBlockIndentWidth: 2\nObjCSpaceAfterProperty: false\nObjCSpaceBeforeProtocolList: false\nPenaltyBreakBeforeFirstCallParameter: 1\nPenaltyBreakComment: 300\nPenaltyBreakFirstLessLess: 120\nPenaltyBreakString: 1000\nPenaltyExcessCharacter: 1000000\nPenaltyReturnTypeOnItsOwnLine: 2000000\nPointerAlignment: Left\nReflowComments:  true\nSortIncludes:    true\nSpaceAfterCStyleCast: false\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeParens: ControlStatements\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 1\nSpacesInAngles:  false\nSpacesInContainerLiterals: true\nSpacesInCStyleCastParentheses: false\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard:        Cpp11\nTabWidth:        8\nUseTab:          Never\n...\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nomit =\n    docs/*\n    tests/*\n    setup.py\n    xformers/benchmarks/*\n    xformers/triton/k_*\n    stubs/*\n    third_party/*\n"
  },
  {
    "path": ".editorconfig",
    "content": "root = true\n\n[*.py]\ncharset = utf-8\ntrim_trailing_whitespace = true\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\n\n[*.md]\ntrim_trailing_whitespace = false\n"
  },
  {
    "path": ".flake8",
    "content": "[flake8]\nexclude =\n    .git\n    ,.github/run-clang-format.py\n    ,third_party\nmax-line-length = 140\ncopyright-check = True\nselect = E,F,W,C\ncopyright-regexp=Copyright \\(c\\) Facebook, Inc. and its affiliates. All Rights Reserved\nignore=W503,E203,E704\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: \"\\U0001F41B Bug Report\"\nabout: Submit a bug report to help us improve xFormers\n\n---\n\n# 🐛 Bug\n\n<!-- A clear and concise description of what the bug is. -->\n\n## Command\n\n## To Reproduce\n\nSteps to reproduce the behavior:\n\n<!-- If you were running a command, post the exact command that you were running -->\n\n1.\n2.\n3.\n\n<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->\n\n## Expected behavior\n\n<!-- A clear and concise description of what you expected to happen. -->\n\n## Environment\n\nPlease copy and paste the output from the\nenvironment collection script from PyTorch\n(or fill out the checklist below manually).\n\nYou can run the script with:\n\n```bash\n# For security purposes, please check the contents of collect_env.py before running it.\npython -m torch.utils.collect_env\n```\n\n- PyTorch Version (e.g., 1.0):\n- OS (e.g., Linux):\n- How you installed PyTorch (`conda`, `pip`, source):\n- Build command you used (if compiling from source):\n- Python version:\n- CUDA/cuDNN version:\n- GPU models and configuration:\n- Any other relevant information:\n\n## Additional context\n\n<!-- Add any other context about the problem here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature-request.md",
    "content": "---\nname: \"\\U0001F680Feature Request\"\nabout: Submit a proposal/request for a new xFormers feature\n\n---\n\n# 🚀 Feature\n\n<!-- A clear and concise description of the feature proposal -->\n\n## Motivation\n\n<!-- Please outline the motivation for the proposal. Is your feature request related to a problem? e.g., I'm always frustrated when [...]. If this is related to another GitHub issue, please link here too -->\n\n## Pitch\n\n<!-- A clear and concise description of what you want to happen. -->\n\n## Alternatives\n\n<!-- A clear and concise description of any alternative solutions or features you've considered, if any. -->\n\n## Additional context\n\n<!-- Add any other context or screenshots about the feature request here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/questions-help-support.md",
    "content": "---\nname: \"❓Questions/Help/Support\"\nabout: Do you need support?\n\n---\n\n# ❓ Questions and Help\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "## What does this PR do?\nFixes # (issue).\n\n## Before submitting\n\n- [ ] Did you have fun?\n  - Make sure you had fun coding 🙃\n- [ ] Did you read the [contributor guideline](https://github.com/facebookresearch/xformers/blob/master/CONTRIBUTING.md)?\n- [ ] Was this discussed/approved via a Github issue? (no need for typos, doc improvements)\n  - [ ] N/A\n- [ ] Did you make sure to update the docs?\n  - [ ] N/A\n- [ ] Did you write any new necessary tests?\n  - [ ] N/A\n- [ ] Did you update the [changelog](https://github.com/facebookresearch/xformers/blob/master/CHANGELOG.md)? (if needed)\n  - [ ] N/A\n\n\n## PR review\nAnyone in the community is free to review the PR once the tests have passed.\nIf we didn't discuss your PR in Github issues there's a high chance it will not be merged.\n"
  },
  {
    "path": ".github/actions/setup-build-cuda/action.yml",
    "content": "name: Set up Runner for build\n\ninputs:\n  toolkit_type:\n    description: cuda or rocm\n    type: string\n  toolkit_short_version:\n    required: true\n    type: string\n    description: \"Example: 117 for 11.7\"\n  python:\n    description: Python version to install\n    type: string\n    default: \"3.10\"\n\nruns:\n  using: composite\n  steps:\n    - id: cuda_info\n      shell: python3 \"{0}\"\n      run: |\n        import os\n        import sys\n        print(sys.version)\n        cushort = \"${{ inputs.toolkit_short_version }}\"\n        # Version uploaded to pypi (rather than PyTorch s3)\n        TORCH_CUDA_DEFAULT = \"128\"  # since pytorch 2.9.0\n        # https://github.com/Jimver/cuda-toolkit/blob/master/src/links/linux-links.ts\n        full_version, install_script = {\n          \"130\": (\"13.0.1\", \"https://developer.download.nvidia.com/compute/cuda/13.0.1/local_installers/cuda_13.0.1_580.82.07_linux.run\"),\n          \"129\": (\"12.9.1\", \"https://developer.download.nvidia.com/compute/cuda/12.9.1/local_installers/cuda_12.9.1_575.57.08_linux.run\"),\n          \"128\": (\"12.8.1\", \"https://developer.download.nvidia.com/compute/cuda/12.8.1/local_installers/cuda_12.8.1_570.124.06_linux.run\"),\n          # (Build with nvcc 12.8 on linux even when building for 12.6 to avoid seg fault in Flash3 build)\n          \"126\": (\"12.8.1\", \"https://developer.download.nvidia.com/compute/cuda/12.8.1/local_installers/cuda_12.8.1_570.124.06_linux.run\"),\n          \"118\": (\"11.8.0\", \"https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run\"),\n          \"6.0\": (\"6.0.2\", \"https://repo.radeon.com/amdgpu-install/6.0.2/rhel/8.9/amdgpu-install-6.0.60002-1.el8.noarch.rpm\"),\n          \"6.1\": (\"6.1.3\", \"https://repo.radeon.com/amdgpu-install/6.1.3/rhel/8.9/amdgpu-install-6.1.60103-1.el8.noarch.rpm\"),\n          \"6.2.4\": (\"6.2.4\", \"https://repo.radeon.com/amdgpu-install/6.2.4/rhel/8.9/amdgpu-install-6.2.60204-1.el8.noarch.rpm\"),\n          \"6.3\": (\"6.3.1\", \"https://repo.radeon.com/amdgpu-install/6.3.1/rhel/8.9/amdgpu-install-6.3.60301-1.el8.noarch.rpm\"),\n          \"6.4\": (\"6.4.2\", \"https://repo.radeon.com/amdgpu-install/6.4.2/rhel/8.9/amdgpu-install-6.4.60402-1.el8.noarch.rpm\"),\n          \"7.0\": (\"7.0.3\", \"https://repo.radeon.com/amdgpu-install/7.0.3/rhel/8/amdgpu-install-7.0.3.70003-1.el8.noarch.rpm\"),\n          \"7.1\": (\"7.1.0\", \"https://repo.radeon.com/amdgpu-install/7.1/rhel/8/amdgpu-install-7.1.70100-1.el8.noarch.rpm\"),\n        }[cushort]\n        with open(os.environ['GITHUB_OUTPUT'], \"r+\") as fp:\n          fp.write(\"CUDA_VERSION=\" + full_version + \"\\n\")\n          if cushort == TORCH_CUDA_DEFAULT:\n            fp.write(\"CUDA_VERSION_SUFFIX=\\n\")\n          else:\n            fp.write(\"CUDA_VERSION_SUFFIX=+\" + (\"cu\" if \"cuda\" == \"${{ inputs.toolkit_type }}\" else \"rocm\") + cushort + \"\\n\")\n          fp.write(\"CUDA_INSTALL_SCRIPT=\" + install_script + \"\\n\")\n    - run: echo \"CUDA_VERSION_SUFFIX=${{ steps.cuda_info.outputs.CUDA_VERSION_SUFFIX }}\" >> ${GITHUB_ENV}\n      shell: bash\n\n    # WINDOWS STEPS\n    - name: Install cuda\n      if: runner.os == 'Windows' && inputs.toolkit_type == 'cuda'\n      id: cuda-toolkit\n      # Using N-Storm fork until https://github.com/Jimver/cuda-toolkit/issues/395 is resolved\n      uses: N-Storm/cuda-toolkit@v0.2.28\n      with:\n        cuda: ${{ steps.cuda_info.outputs.CUDA_VERSION }}\n        method: network\n    - if: runner.os == 'Windows' && inputs.toolkit_type == 'cuda'\n      shell: bash\n      run: |\n        echo \"Installed cuda version is: ${{ steps.cuda-toolkit.outputs.cuda }}\"\n        echo \"Cuda install location: ${{ steps.cuda-toolkit.outputs.CUDA_PATH }}\"\n        echo \"CUDA_HOME=${{ steps.cuda-toolkit.outputs.CUDA_PATH }}\" >> ${GITHUB_ENV}\n        cat ${GITHUB_ENV}\n\n    - name: Install python\n      if: runner.os == 'Windows'\n      uses: actions/setup-python@v4\n      with:\n        python-version: ${{ inputs.python }}\n\n    - name: Setup MSVC\n      if: runner.os == 'Windows'\n      uses: ilammy/msvc-dev-cmd@v1\n\n    # really unfortunate: https://github.com/ilammy/msvc-dev-cmd#name-conflicts-with-shell-bash\n    - name: Remove link.exe\n      if: runner.os == 'Windows'\n      shell: bash\n      run: rm /usr/bin/link\n\n    # LINUX STEPS\n    - if: ${{ runner.os == 'Linux' && !(contains(inputs.toolkit_type, 'cuda') && fromJSON(inputs.toolkit_short_version) > 124) }}\n      shell: bash\n      run: |\n        # Use GCC11 for ROCM / cu118 / cu124\n        yum list installed\n        yum install gcc-toolset-11-gcc gcc-toolset-11-gcc-c++ gcc-toolset-11-libstdc++-devel wget git -y\n        echo \"source /opt/rh/gcc-toolset-11/enable\" >> ~/.profile\n\n    - if: ${{ runner.os == 'Linux' && contains(inputs.toolkit_type, 'cuda') && fromJSON(inputs.toolkit_short_version) > 124 }}\n      shell: bash\n      run: |\n        # Use GCC13 for cu126+\n        yum list installed\n        yum install gcc-toolset-13-gcc gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel wget git -y\n        echo \"source /opt/rh/gcc-toolset-13/enable\" >> ~/.profile\n\n    - if: runner.os == 'Linux'\n      shell: bash -l {0}\n      run: |\n        yum list installed\n        yum install wget git -y\n        which g++\n        g++ --version\n\n    - if: runner.os == 'Linux' && contains(inputs.toolkit_type, 'cuda')\n      name: (Linux) install cuda\n      shell: bash -l {0}\n      run: |\n        wget -q \"${{ steps.cuda_info.outputs.CUDA_INSTALL_SCRIPT }}\" -O cuda.run && \\\n        sh ./cuda.run --silent --toolkit && \\\n        rm ./cuda.run\n        echo \"CUDA_HOME=/usr/local/cuda\" >> ${GITHUB_ENV}\n\n    - if: runner.os == 'Linux' && contains(inputs.toolkit_type, 'cuda')\n      name: (Linux) print cuda setup info\n      shell: bash -l {0}\n      run: |\n        echo \"CUDA_HOME=$CUDA_HOME\"\n        echo \"###############################\"\n        echo \"############ NVCC  ############\"\n        echo \"###############################\"\n        $CUDA_HOME/bin/nvcc --version\n        md5sum $CUDA_HOME/bin/nvcc\n        echo \"###############################\"\n        echo \"############ PTXAS ############\"\n        echo \"###############################\"\n        $CUDA_HOME/bin/ptxas --version\n        md5sum $CUDA_HOME/bin/ptxas\n\n    - if: runner.os == 'Linux' && contains(inputs.toolkit_type, 'rocm')\n      name: (Linux) install rocm\n      shell: bash\n      run: |\n        yum install -y libzstd\n        yum install -y ${{ steps.cuda_info.outputs.CUDA_INSTALL_SCRIPT }}\n        amdgpu-install -y --usecase=rocm --no-dkms\n        echo \"ROCM_PATH=/opt/rocm\" >> ${GITHUB_ENV}\n        echo \"PATH=$PATH:/opt/rocm/bin\" >> ${GITHUB_ENV}\n        echo \"MAX_JOBS=16\" >> ${GITHUB_ENV}\n\n    # host compiler is too new for cuda 12.1 :(\n    - run: echo \"NVCC_FLAGS=-allow-unsupported-compiler\" >> $GITHUB_ENV\n      shell: bash\n"
  },
  {
    "path": ".github/actions/setup-env-build/action.yml",
    "content": "name: Install env + build\ninputs:\n  arch:\n    description: 'GPU architecture'\n    required: true\n  python:\n    description: 'Python version'\n    required: false\n    default: \"3.11\"\n\nruns:\n  using: composite\n  steps:\n    - name: Cleanup\n      shell: bash\n      run: rm -f ~/.profile ~/.bashrc\n    - id: prepare_conda_env_paths\n      shell: python\n      run: |\n        import os\n        import subprocess\n        import hashlib\n        import glob\n        import datetime\n        from pathlib import Path\n\n        CONDA_INSTALL_CMD = \"micromamba create python=${{ inputs.python }} zlib pip ninja ccache=4.8 -c conda-forge -q -y\"\n\n        conda_env_key = CONDA_INSTALL_CMD + \"[cu130][v2]\"\n        for file in sorted(glob.glob(\"requirement*.txt\")):\n          conda_env_key += f\"\\n########## {file}\\n\"\n          conda_env_key += Path(file).read_text()\n        env_name_key = hashlib.sha224(conda_env_key.encode(\"ascii\")).hexdigest()[:8]\n        env_name_key += \"-${{ inputs.arch }}\"\n        # Nightly or Test, update every week\n        env_name_key += \"-\"+datetime.date.today().strftime(\"%Y-week%W\")\n        shared_dir = os.environ.get(\"GHRUNNER_SHARED_DIR\", os.getcwd())\n        env_path = os.path.join(shared_dir, \"tmp\", \"${{ inputs.arch }}\", os.environ[\"GITHUB_RUN_ID\"])\n        final_env = Path(shared_dir) / f\"env_{env_name_key}.txt\"\n        pkg_dir = Path(shared_dir) / \"pkgs-sm${{ inputs.arch }}\"\n        (Path(shared_dir) / f\"env_{env_name_key}_content.txt\").write_text(conda_env_key)\n        CONDA_INSTALL_CMD += \" -p \" + env_path\n        env_already_built = False\n        # If environment is already built\n        if final_env.is_file():\n          final_env_link = final_env.read_text().strip()\n          if (Path(final_env_link) / \"bin\" / \"python\").is_file():\n            print(\"Found valid env - skipping env setup\")\n            CONDA_INSTALL_CMD = \"true\"\n            env_already_built = True\n            env_path = final_env_link\n          else:\n            print(\"Invalid env\")\n        with open(os.environ['GITHUB_ENV'], \"r+\") as fp:\n            fp.write(\"CONDA_ENV_LINK=\" + str(final_env) + \"\\n\")\n            fp.write(\"CONDA_PREFIX=\" + env_path + \"\\n\")\n            fp.write(\"CONDA_PKGS_DIRS=\" + str(pkg_dir) + \"\\n\")\n            fp.write(\"CONDA_INSTALL_CMD=\" + CONDA_INSTALL_CMD + \"\\n\")\n            fp.write(\"CONDA_ENV_HASH=\" + env_name_key + \"\\n\")\n            fp.write(\"PY=\" + os.path.join(env_path, \"bin\", \"python\") + \"\\n\")\n            fp.write(\"PIP=\" + os.path.join(env_path, \"bin\", \"pip\") + \"\\n\")\n        with open(os.environ['GITHUB_OUTPUT'], \"r+\") as fp:\n          fp.write(f\"ENV_CACHED={int(env_already_built)}\\n\")\n    - name: Print conda commands\n      shell: bash -l {0}\n      run: |\n        echo \"CONDA_PREFIX=$CONDA_PREFIX\"\n        echo \"CONDA_INSTALL_CMD=$CONDA_INSTALL_CMD\"\n        echo \"CONDA_ENV_HASH=$CONDA_ENV_HASH\"\n        echo \"PY=$PY\"\n    - name: Install micromamba\n      shell: bash -l {0}\n      run: |\n        set -ex\n        curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest -o micromamba.tar.bz2\n        tar --extract --verbose --bzip2 --file=micromamba.tar.bz2 bin/micromamba\n        echo \"eval \\\"\\$($(pwd)/bin/micromamba shell hook --shell bash)\\\"\" >> ~/.profile\n    - name: Conda/pip setup\n      shell: bash -l {0}\n      if: steps.prepare_conda_env_paths.outputs.ENV_CACHED == 0\n      run: |\n        set -ex\n        micromamba config set channel_priority strict\n        # Retry if failed after removing downloaded packages cache\n        $CONDA_INSTALL_CMD || (rm -rf $CONDA_PKGS_DIRS && rm -rf $CONDA_PREFIX && $CONDA_INSTALL_CMD)\n        $PY -m pip install cmake\n        $PY -m pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu128\n        $PY -m pip install -r requirements-benchmark.txt --progress-bar off\n    - name: Activate environment\n      shell: bash -l {0}\n      run: |\n        echo \"micromamba activate $CONDA_PREFIX\" >> ~/.profile\n        echo \"==== .profile =====\"\n        cat ~/.profile\n    - run: which python\n      shell: bash -l {0}\n    - name: Setup ccache nvcc\n      shell: bash -l {0}\n      if: steps.prepare_conda_env_paths.outputs.ENV_CACHED == 0\n      run: |\n        echo \"#!/bin/bash\" > $CONDA_PREFIX/bin/nvcc-ccache\n        echo \"ccache nvcc \\\"\\$@\\\"\" >> $CONDA_PREFIX/bin/nvcc-ccache\n        cat $CONDA_PREFIX/bin/nvcc-ccache\n        chmod +x $CONDA_PREFIX/bin/nvcc-ccache\n        which nvcc\n        ccache --version\n\n    - name: Setup ccache g++\n      shell: bash -l {0}\n      if: steps.prepare_conda_env_paths.outputs.ENV_CACHED == 0\n      run: |\n        echo \"#!/bin/bash\" > $CONDA_PREFIX/bin/g++-ccache\n        echo \"ccache g++ \\\"\\$@\\\"\" >> $CONDA_PREFIX/bin/g++-ccache\n        cat $CONDA_PREFIX/bin/g++-ccache\n        chmod +x $CONDA_PREFIX/bin/g++-ccache\n        which g++-ccache\n\n    - name: Patch for https://github.com/pytorch/pytorch/issues/114962\n      shell: bash -l {0}\n      run: |\n        CPP_EXTENSIONS_PY=$(python -c \"import torch.utils.cpp_extension; print(torch.utils.cpp_extension.__file__)\")\n        echo \"Patching $CPP_EXTENSIONS_PY\"\n        sed -i \"/generate-dependencies-with-compile/d\" $CPP_EXTENSIONS_PY\n    - name: Check NVIDIA libs\n      shell: bash -l {0}\n      run: |\n        ldconfig -p | grep libcuda.so\n        ls /.singularity.d/libs/\n    - name: Mark env as ready\n      shell: bash -l {0}\n      if: steps.prepare_conda_env_paths.outputs.ENV_CACHED == 0\n      run: echo $CONDA_PREFIX > $CONDA_ENV_LINK\n    - name: Setup ccache\n      shell: bash -l {0}\n      run: |\n        export CCACHE_DIR=$GHRUNNER_SHARED_DIR/ccache\n        echo \"CCACHE_DIR=$CCACHE_DIR\" >> ${GITHUB_ENV}\n        mkdir -p $CCACHE_DIR\n        ccache -s\n    - name: Build\n      shell: bash -l {0}\n      run: |\n        PYTORCH_NVCC=\"$CONDA_PREFIX/bin/nvcc-ccache\" CXX=\"g++-ccache\" TORCH_CUDA_ARCH_LIST=${{ inputs.arch }} python -m pip install -v --no-build-isolation -e .\n    - name: Check for PyTorch stable symbols\n      shell: bash -l {0}\n      run: |\n        bad_symbols=$(nm --dynamic --undefined-only --demangle xformers/_C.so | grep --extended-regexp \"(torch|at|c10|c10d)::\" || true)\n        if [[ $bad_symbols != \"\" ]]; then echo \"These non-stable PyTorch symbols made it into the xFormers shared library:\"; echo $bad_symbols; exit 1; fi\n    - name: Build info\n      run: |\n        printenv\n        python -m xformers.info\n        python xformers/_triton_version_fairinternal.py\n        ccache -s\n      shell: bash -l {0}\n"
  },
  {
    "path": ".github/compute_wheel_version.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nimport argparse\nimport subprocess\nfrom pathlib import Path\nfrom typing import Optional\n\n# TODO: consolidate with the code in build_conda.py\nTHIS_PATH = Path(__file__).resolve()\nversion_from_file = (THIS_PATH.parents[1] / \"version.txt\").read_text().strip()\n\n\ndef get_tagged_version() -> Optional[str]:\n    \"\"\"\n    Return whether we are at an exact version (namely the version variable).\n    \"\"\"\n    try:\n        tag = subprocess.check_output(\n            [\"git\", \"describe\", \"--tags\", \"--exact-match\", \"HEAD\"],\n            text=True,\n            stderr=subprocess.DEVNULL,\n        ).strip()\n    except subprocess.CalledProcessError:  # no tag\n        return None\n\n    if not tag.startswith(\"v\"):\n        return None\n    return tag[1:]\n\n\ndef get_dev_version() -> str:\n    assert \".dev\" not in version_from_file\n    num_commits = subprocess.check_output(\n        [\"git\", \"rev-list\", \"--count\", \"HEAD\"], text=True\n    ).strip()\n    return f\"{version_from_file}.dev{num_commits}\"\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        \"--source\", choices=[\"tag\", \"dev\", \"tag,dev\"], required=False, default=\"tag,dev\"\n    )\n    args = parser.parse_args()\n\n    if \"tag\" in args.source:\n        tagged_version = get_tagged_version()\n        if args.source == \"tag\" and tagged_version is None:\n            raise ValueError(\"No tag found\")\n    else:\n        tagged_version = None\n    if tagged_version is not None:\n        print(tagged_version, end=\"\")\n    else:\n        print(get_dev_version(), end=\"\")\n"
  },
  {
    "path": ".github/gpu_benchmark_diff.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport glob\nimport os\nimport subprocess\n\nimport xformers.benchmarks.utils as utils\n\n\nclass NamedObject:\n    def __init__(self, name) -> None:\n        self.__name__ = name\n\n\ndef git_file_at(filename: str, ref: str) -> str:\n    try:\n        return subprocess.check_output(\n            [\"git\", \"show\", f\"{ref}:{filename}\"], text=True\n        ).strip()\n    except subprocess.CalledProcessError:\n        return \"\"  # File does not exist in that revision\n\n\nGITHUB_BASE_REF = subprocess.check_output(\n    [\"git\", \"rev-parse\", \"origin/\" + os.environ[\"GITHUB_BASE_REF\"]], text=True\n).strip()\nXFORMERS_BENCHMARKS_CACHE = os.environ[\"XFORMERS_BENCHMARKS_CACHE\"]\nGITHUB_CURRENT_REF = subprocess.check_output(\n    [\"git\", \"rev-parse\", \"HEAD\"], text=True\n).strip()\n\nfor f in glob.glob(os.path.join(XFORMERS_BENCHMARKS_CACHE, \"*\", \"*.csv\")):\n    before = git_file_at(f, ref=GITHUB_BASE_REF)\n    now = git_file_at(f, ref=GITHUB_CURRENT_REF)\n    if before == \"\" or before == now:\n        continue\n    benchmark_name = os.path.basename(os.path.dirname(f))\n\n    print(\"#\" * 100)\n    print(f\"# UPDATED: {f}\")\n    print(\"#\" * 100)\n\n    filename_before = f.replace(\"reference\", \"before\")\n    filename_now = f.replace(\"reference\", \"now\")\n    with open(filename_before, \"w+\") as fd:\n        fd.write(before)\n    with open(filename_now, \"w+\") as fd:\n        fd.write(now)\n    utils.benchmark_run_and_compare(\n        benchmark_fn=NamedObject(benchmark_name),\n        cases=[],\n        compare=[\n            os.path.basename(filename_before)[: -len(\".csv\")],\n            os.path.basename(filename_now)[: -len(\".csv\")],\n        ],\n    )\n"
  },
  {
    "path": ".github/run-clang-format.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nMIT License\nCopyright (c) 2017 Guillaume Papin\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\"\"\"\n\n\"\"\"A wrapper script around clang-format, suitable for linting multiple files\nand to use for continuous integration.\nThis is an alternative API for the clang-format command line.\nIt runs over multiple files and directories in parallel.\nA diff output is produced and a sensible exit code is returned.\n\"\"\"\n\nimport argparse  # noqa: E402\nimport difflib  # noqa: E402\nimport fnmatch  # noqa: E402\nimport io  # noqa: E402\nimport multiprocessing  # noqa: E402\nimport os  # noqa: E402\nimport signal  # noqa: E402\nimport subprocess  # noqa: E402\nimport sys  # noqa: E402\nimport traceback  # noqa: E402\nfrom functools import partial  # noqa: E402\nfrom subprocess import DEVNULL  # noqa: E402\n\nDEFAULT_EXTENSIONS = \"c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx,cu\"\n\n\nclass ExitStatus:\n    SUCCESS = 0\n    DIFF = 1\n    TROUBLE = 2\n\n\ndef list_files(files, recursive=False, extensions=None, exclude=None):\n    if extensions is None:\n        extensions = []\n    if exclude is None:\n        exclude = []\n\n    out = []\n    for file in files:\n        if recursive and os.path.isdir(file):\n            for dirpath, dnames, fnames in os.walk(file):\n                fpaths = [os.path.join(dirpath, fname) for fname in fnames]\n                for pattern in exclude:\n                    # os.walk() supports trimming down the dnames list\n                    # by modifying it in-place,\n                    # to avoid unnecessary directory listings.\n                    dnames[:] = [\n                        x\n                        for x in dnames\n                        if not fnmatch.fnmatch(os.path.join(dirpath, x), pattern)\n                    ]\n                    fpaths = [x for x in fpaths if not fnmatch.fnmatch(x, pattern)]\n                for f in fpaths:\n                    ext = os.path.splitext(f)[1][1:]\n                    if ext in extensions:\n                        out.append(f)\n        else:\n            out.append(file)\n    return out\n\n\ndef make_diff(file, original, reformatted):\n    return list(\n        difflib.unified_diff(\n            original,\n            reformatted,\n            fromfile=\"a/{}\\t(original)\".format(file),\n            tofile=\"b/{}\\t(reformatted)\".format(file),\n            n=3,\n        )\n    )\n\n\nclass DiffError(Exception):\n    def __init__(self, message, errs=None):\n        super(DiffError, self).__init__(message)\n        self.errs = errs or []\n\n\nclass UnexpectedError(Exception):\n    def __init__(self, message, exc=None):\n        super(UnexpectedError, self).__init__(message)\n        self.formatted_traceback = traceback.format_exc()\n        self.exc = exc\n\n\ndef run_clang_format_diff_wrapper(args, file):\n    try:\n        ret = run_clang_format_diff(args, file)\n        return ret\n    except DiffError:\n        raise\n    except Exception as e:\n        raise UnexpectedError(\"{}: {}: {}\".format(file, e.__class__.__name__, e), e)\n\n\ndef run_clang_format_diff(args, file):\n    try:\n        with io.open(file, \"r\", encoding=\"utf-8\") as f:\n            original = f.readlines()\n    except IOError as exc:\n        raise DiffError(str(exc))\n    invocation = [args.clang_format_executable, file]\n\n    # Use of utf-8 to decode the process output.\n    #\n    # Hopefully, this is the correct thing to do.\n    #\n    # It's done due to the following assumptions (which may be incorrect):\n    # - clang-format will returns the bytes read from the files as-is,\n    #   without conversion, and it is already assumed that the files use utf-8.\n    # - if the diagnostics were internationalized, they would use utf-8:\n    #   > Adding Translations to Clang\n    #   >\n    #   > Not possible yet!\n    #   > Diagnostic strings should be written in UTF-8,\n    #   > the client can translate to the relevant code page if needed.\n    #   > Each translation completely replaces the format string\n    #   > for the diagnostic.\n    #   > -- http://clang.llvm.org/docs/InternalsManual.html#internals-diag-translation\n\n    try:\n        proc = subprocess.Popen(\n            invocation,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.PIPE,\n            universal_newlines=True,\n            encoding=\"utf-8\",\n        )\n    except OSError as exc:\n        raise DiffError(\n            \"Command '{}' failed to start: {}\".format(\n                subprocess.list2cmdline(invocation), exc\n            )\n        )\n    proc_stdout = proc.stdout\n    proc_stderr = proc.stderr\n\n    # hopefully the stderr pipe won't get full and block the process\n    outs = list(proc_stdout.readlines())\n    errs = list(proc_stderr.readlines())\n    proc.wait()\n    if proc.returncode:\n        raise DiffError(\n            \"Command '{}' returned non-zero exit status {}\".format(\n                subprocess.list2cmdline(invocation), proc.returncode\n            ),\n            errs,\n        )\n    return make_diff(file, original, outs), errs\n\n\ndef bold_red(s):\n    return \"\\x1b[1m\\x1b[31m\" + s + \"\\x1b[0m\"\n\n\ndef colorize(diff_lines):\n    def bold(s):\n        return \"\\x1b[1m\" + s + \"\\x1b[0m\"\n\n    def cyan(s):\n        return \"\\x1b[36m\" + s + \"\\x1b[0m\"\n\n    def green(s):\n        return \"\\x1b[32m\" + s + \"\\x1b[0m\"\n\n    def red(s):\n        return \"\\x1b[31m\" + s + \"\\x1b[0m\"\n\n    for line in diff_lines:\n        if line[:4] in [\"--- \", \"+++ \"]:\n            yield bold(line)\n        elif line.startswith(\"@@ \"):\n            yield cyan(line)\n        elif line.startswith(\"+\"):\n            yield green(line)\n        elif line.startswith(\"-\"):\n            yield red(line)\n        else:\n            yield line\n\n\ndef print_diff(diff_lines, use_color):\n    if use_color:\n        diff_lines = colorize(diff_lines)\n    sys.stdout.writelines(diff_lines)\n\n\ndef print_trouble(prog, message, use_colors):\n    error_text = \"error:\"\n    if use_colors:\n        error_text = bold_red(error_text)\n    print(\"{}: {} {}\".format(prog, error_text, message), file=sys.stderr)\n\n\ndef main():\n    parser = argparse.ArgumentParser(description=__doc__)\n    parser.add_argument(\n        \"--clang-format-executable\",\n        metavar=\"EXECUTABLE\",\n        help=\"path to the clang-format executable\",\n        default=\"clang-format\",\n    )\n    parser.add_argument(\n        \"--extensions\",\n        help=\"comma separated list of file extensions (default: {})\".format(\n            DEFAULT_EXTENSIONS\n        ),\n        default=DEFAULT_EXTENSIONS,\n    )\n    parser.add_argument(\n        \"-r\",\n        \"--recursive\",\n        action=\"store_true\",\n        help=\"run recursively over directories\",\n    )\n    parser.add_argument(\"files\", metavar=\"file\", nargs=\"+\")\n    parser.add_argument(\"-q\", \"--quiet\", action=\"store_true\")\n    parser.add_argument(\n        \"-j\",\n        metavar=\"N\",\n        type=int,\n        default=0,\n        help=\"run N clang-format jobs in parallel\" \" (default number of cpus + 1)\",\n    )\n    parser.add_argument(\n        \"--color\",\n        default=\"auto\",\n        choices=[\"auto\", \"always\", \"never\"],\n        help=\"show colored diff (default: auto)\",\n    )\n    parser.add_argument(\n        \"-e\",\n        \"--exclude\",\n        metavar=\"PATTERN\",\n        action=\"append\",\n        default=[],\n        help=\"exclude paths matching the given glob-like pattern(s)\"\n        \" from recursive search\",\n    )\n\n    args = parser.parse_args()\n\n    # use default signal handling, like diff return SIGINT value on ^C\n    # https://bugs.python.org/issue14229#msg156446\n    signal.signal(signal.SIGINT, signal.SIG_DFL)\n    try:\n        signal.SIGPIPE\n    except AttributeError:\n        # compatibility, SIGPIPE does not exist on Windows\n        pass\n    else:\n        signal.signal(signal.SIGPIPE, signal.SIG_DFL)\n\n    colored_stdout = False\n    colored_stderr = False\n    if args.color == \"always\":\n        colored_stdout = True\n        colored_stderr = True\n    elif args.color == \"auto\":\n        colored_stdout = sys.stdout.isatty()\n        colored_stderr = sys.stderr.isatty()\n\n    version_invocation = [args.clang_format_executable, str(\"--version\")]\n    try:\n        subprocess.check_call(version_invocation, stdout=DEVNULL)\n    except subprocess.CalledProcessError as e:\n        print_trouble(parser.prog, str(e), use_colors=colored_stderr)\n        return ExitStatus.TROUBLE\n    except OSError as e:\n        print_trouble(\n            parser.prog,\n            \"Command '{}' failed to start: {}\".format(\n                subprocess.list2cmdline(version_invocation), e\n            ),\n            use_colors=colored_stderr,\n        )\n        return ExitStatus.TROUBLE\n\n    retcode = ExitStatus.SUCCESS\n    files = list_files(\n        args.files,\n        recursive=args.recursive,\n        exclude=args.exclude,\n        extensions=args.extensions.split(\",\"),\n    )\n\n    if not files:\n        return\n\n    njobs = args.j\n    if njobs == 0:\n        njobs = multiprocessing.cpu_count() + 1\n    njobs = min(len(files), njobs)\n\n    if njobs == 1:\n        # execute directly instead of in a pool,\n        # less overhead, simpler stacktraces\n        it = (run_clang_format_diff_wrapper(args, file) for file in files)\n        pool = None\n    else:\n        pool = multiprocessing.Pool(njobs)\n        it = pool.imap_unordered(partial(run_clang_format_diff_wrapper, args), files)\n    while True:\n        try:\n            outs, errs = next(it)\n        except StopIteration:\n            break\n        except DiffError as e:\n            print_trouble(parser.prog, str(e), use_colors=colored_stderr)\n            retcode = ExitStatus.TROUBLE\n            sys.stderr.writelines(e.errs)\n        except UnexpectedError as e:\n            print_trouble(parser.prog, str(e), use_colors=colored_stderr)\n            sys.stderr.write(e.formatted_traceback)\n            retcode = ExitStatus.TROUBLE\n            # stop at the first unexpected error,\n            # something could be very wrong,\n            # don't process all files unnecessarily\n            if pool:\n                pool.terminate()\n            break\n        else:\n            sys.stderr.writelines(errs)\n            if outs == []:\n                continue\n            if not args.quiet:\n                print_diff(outs, use_color=colored_stdout)\n            if retcode == ExitStatus.SUCCESS:\n                retcode = ExitStatus.DIFF\n    return retcode\n\n\nif __name__ == \"__main__\":\n    sys.exit(main())\n"
  },
  {
    "path": ".github/run_benchmark_wrapper.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport glob\nimport os\nimport shlex\nimport subprocess\nimport sys\n\nimport torch\n\nimport xformers\n\n# Build failed - return early\nif not xformers._has_cpp_library:\n    print(\"xFormers wasn't built correctly - can't run benchmarks\")\n    sys.exit(0)\n\nbenchmark_script = os.path.join(\"xformers\", \"benchmarks\", sys.argv[1])\nbenchmark_fn = sys.argv[2]\nlabel = subprocess.check_output([\"git\", \"rev-parse\", \"HEAD\"], text=True).strip()[:8]\ncmd = [\n    sys.executable,\n    benchmark_script,\n    \"--label\",\n    label,\n    \"--fn\",\n    benchmark_fn,\n    \"--fail_if_regression\",\n    \"--quiet\",\n]\nenv = (\n    torch.cuda.get_device_name(torch.cuda.current_device())\n    .replace(\" \", \"_\")\n    .replace(\"-\", \"_\")\n    .replace(\".\", \"_\")\n)\n\n# Figure out the name of the baseline\npattern = os.path.join(os.environ[\"XFORMERS_BENCHMARKS_CACHE\"], benchmark_fn, \"*.csv\")\nref_names = glob.glob(pattern)\nbaseline_names = set(\n    os.path.basename(s)[: -len(\".csv\")]\n    for s in ref_names\n    # Only compare to benchmark data on same hardware\n    if env in os.path.basename(s)\n)\nif baseline_names:\n    if len(baseline_names) > 1:\n        raise RuntimeError(\n            f\"Supplied more than one reference for this benchmark: {','.join(baseline_names)}\"\n        )\n    cmd += [\"--compare\", \",\".join(baseline_names)]\n\nprint(\"EXEC:\", shlex.join(cmd))\n\nretcode = 0\ntry:\n    subprocess.check_call(cmd)\nexcept subprocess.CalledProcessError as e:\n    retcode = e.returncode\n\n# Remove original benchmark files\nfor f in ref_names:\n    os.remove(f)\n# Rename new ones as 'ref'\nfor f in glob.glob(pattern):\n    os.rename(f, f.replace(label, \"reference\"))\n\nsys.exit(retcode)\n"
  },
  {
    "path": ".github/selective_ci/requirements.txt",
    "content": "GitPython\n"
  },
  {
    "path": ".github/selective_ci/selective_ci.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport fnmatch\nimport os\nfrom dataclasses import dataclass, field\nfrom pathlib import Path\n\nimport git\n\n\n@dataclass\nclass ComponentInfo:\n    \"\"\"\n    A component is deemed to have changed if any of its\n    files or dependencies have changed.\n    If it has not changed, its files will be removed.\n    \"\"\"\n\n    name: str\n    # These files will be deleted if the component is not enabled\n    files: list[str]\n    dependencies: list[str]\n    disable_set_env: dict[str, str] = field(default_factory=dict)\n\n\nCOMMON_PATTERNS = [\n    # All components will be tested if something in there changes\n    \"setup.py\",\n]\n\nCOMPONENTS = [\n    ComponentInfo(\n        name=\"attention\",\n        files=[\n            \"tests/test_mem_eff_attention.py\",\n            \"tests/test_find_sparse_locations*.py\",\n            \"tests/test_block_sparse_mem_eff_attention*.py\",\n            \"tests/test_attention_patterns.py\",\n            \"tests/test_rope_padded.py\",\n            \"tests/test_tree_attention*.py\",\n            \"tests/test_fmha*.py\",\n        ],\n        dependencies=[\n            \"xformers/ops/fmha/*\",\n            \"third_party/cutlass\",\n            \"third_party/composable_kernel_tiled\",\n            \"xformers/csrc/attention/*\",\n            \"xformers/triton/*\",\n        ],\n        disable_set_env={\n            \"XFORMERS_DISABLE_FLASH_ATTN\": \"1\",\n        },\n    ),\n    ComponentInfo(\n        name=\"sp24\",\n        files=[\n            \"tests/test_sparsity24.py\",\n            \"xformers/csrc/sparse24/*\",\n        ],\n        dependencies=[\n            \"xformers/ops/sp24.py\",\n        ],\n    ),\n    ComponentInfo(\n        name=\"sequence_parallel_fused\",\n        files=[\n            \"tests/test_seqpar.py\",\n            \"tests/test_sequence_parallel_fused_ops.py\",\n            \"tests/test_tiled_matmul.py\",\n        ],\n        dependencies=[\n            \"tests/multiprocessing_utils.py\",\n            \"xformers/ops/sequence_parallel_fused_ops.py\",\n        ],\n    ),\n]\n\nrepo_root_path = Path(__file__).parent.parent.parent.resolve().absolute()\nrepo = git.Repo(repo_root_path)\n\n\ndef list_files_in_commit(commit: git.Commit):\n    file_list = []\n    stack = [commit.tree]\n    while len(stack) > 0:\n        tree = stack.pop()\n        # enumerate blobs (files) at this level\n        for b in tree.blobs:\n            file_list.append(str(Path(b.path).absolute().relative_to(repo_root_path)))\n        for subtree in tree.trees:\n            stack.append(subtree)\n    # you can return dir_list if you want directories too\n    return file_list\n\n\ndef check_patterns_are_valid(patterns):\n    # Only check patterns in `fairinternal` repo\n    if os.environ.get(\"GITHUB_REPOSITORY\", \"\") != \"fairinternal/xformers\":\n        return\n    found_patterns = set()\n    for f in all_files:\n        for pattern in patterns:\n            if fnmatch.fnmatch(f, pattern):\n                found_patterns.add(pattern)\n    for pattern in patterns:\n        if pattern not in found_patterns:\n            assert False, f\"Pattern does not match any file: `{pattern}`\"\n\n\nparser = argparse.ArgumentParser(\"xFormers selective CI\")\nparser.add_argument(\"--base_commit\", default=\"origin/main\")\nargs = parser.parse_args()\n\nbase_commit = repo.rev_parse(args.base_commit)\nall_files = list_files_in_commit(repo.head.commit) + [sm.path for sm in repo.submodules]\nall_modified_files = set()\nfor item in repo.head.commit.diff(base_commit):\n    if item.a_path is not None:\n        all_modified_files.add(item.a_path)\n    if item.b_path is not None:\n        all_modified_files.add(item.b_path)\n\ncheck_patterns_are_valid(COMMON_PATTERNS)\nfor component in COMPONENTS:\n    # Sanity check that all files exist\n    check_patterns_are_valid(component.files + component.dependencies)\n\n    # Check if module is updated\n    skip_module = True\n    for pattern in COMMON_PATTERNS + component.files + component.dependencies:\n        for f in all_modified_files:\n            if fnmatch.fnmatch(f, pattern):\n                skip_module = False\n                break\n    print(component.name, \"SKIP\" if skip_module else \"TEST\")\n    if not skip_module:\n        continue\n\n    # Delete component files\n    for f in all_files:\n        for pattern in component.files:\n            if fnmatch.fnmatch(f, pattern):\n                if Path(f).exists():\n                    Path(f).unlink()\n\n    # Set env variable\n    for env_k, env_v in component.disable_set_env.items():\n        if \"GITHUB_ENV\" not in os.environ:\n            print(f\"{env_k}={env_v}\")\n            continue\n        with open(os.environ[\"GITHUB_ENV\"], \"a\") as fd:\n            fd.write(f\"{env_k}={env_v}\\n\")\n"
  },
  {
    "path": ".github/workflows/gh-pages.yml",
    "content": "name: Build & deploy documentation\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n\njobs:\n  deploy:\n    runs-on: ubuntu-24.04\n    concurrency:\n      group: ${{ github.workflow }}-${{ github.ref }}\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Setup Python\n        uses: actions/setup-python@v2\n        with:\n          python-version: '3.9'\n\n      - name: Upgrade pip\n        run: |\n          # install pip=>20.1 to use \"pip cache dir\"\n          python3 -m pip install --upgrade pip\n\n      - name: Get pip cache dir\n        id: pip-cache\n        run: echo \"::set-output name=dir::$(pip cache dir)\"\n\n      - name: Cache dependencies\n        uses: actions/cache@v4\n        with:\n          path: ${{ steps.pip-cache.outputs.dir }}\n          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}\n          restore-keys: |\n            ${{ runner.os }}-pip-\n\n      - name: Build docs\n\n        run: |\n          cd docs\n          pip install --progress-bar off -r requirements.txt\n          make help\n          make html\n\n      - name: Deploy\n        uses: peaceiris/actions-gh-pages@v3\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_dir: docs/build/html\n        if: github.event_name != 'pull_request'\n"
  },
  {
    "path": ".github/workflows/gpu_test_gh.yml",
    "content": "name: gpu_test_gh\n\non:\n  workflow_dispatch: {}\n  pull_request:\n    paths:\n      - \"xformers/**\"\n      - \"!xformers/benchmarks/**\"\n      - \"!xformers/version.txt\"\n      - \".github/workflows/gpu_test_gh*\"\n      - \"tests/**\"\n      - \"setup.py\"\n      - \"requirements*.txt\"\n      - \"third_party/**\"\n  push:\n    branches:\n      - main\n\nenv:\n  XFORMERS_BUILD_TYPE: \"Release\"\n  CI: \"1\"\n  TORCHINDUCTOR_COMPILE_THREADS: \"1\"\n\njobs:\n  gpu_test_gh:\n    strategy:\n      fail-fast: false\n      matrix:\n        gpu:\n          - runner: \"h100-256GB\"\n            sm: \"9.0a\"\n          - runner: \"4-core-ubuntu-gpu-t4\"\n            sm: \"7.5\"\n        python: [3.11]\n\n    name: test_sm${{ matrix.gpu.sm }}\n    runs-on: ${{ matrix.gpu.runner }}\n\n    timeout-minutes: 360\n    defaults:\n      run:\n        shell: bash -l {0}\n    steps:\n      - name: Recursive checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n          path: \".\"\n          fetch-depth: 0 # We need commits history as well\n      - run: nvidia-smi\n      - name: Install micromamba\n        run: |\n          set -ex\n          curl -Ls https://micro.mamba.pm/api/micromamba/linux-64/latest | tar -xvj bin/micromamba\n          echo \"eval \\\"\\$($(pwd)/bin/micromamba shell hook --shell bash)\\\"\" >> ~/.profile\n          cat ~/.profile\n      - name: Create environment\n        run: |\n          set -ex\n          micromamba config set channel_priority strict\n          micromamba create -n env python=${{ matrix.python }} \\\n            zlib pip ninja ccache=4.8 cuda-toolkit \\\n            -c \"nvidia/label/cuda-12.6\" -c conda-forge -q -y\n      - name: Activate environment\n        shell: bash -l {0}\n        run: |\n          echo \"micromamba activate env\" >> ~/.profile\n          echo \"==== .profile =====\"\n          cat ~/.profile\n      - name: Selective build/tests\n        if: github.event_name == 'pull_request'\n        run: |\n          pip install -r .github/selective_ci/requirements.txt\n          python .github/selective_ci/selective_ci.py --base_commit ${{ github.event.pull_request.base.sha }}\n      - name: Setup test requirements\n        run: |\n          which python\n          which nvcc\n          pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu126\n          pip install --pre flash_attn_3 --index-url https://download.pytorch.org/whl/cu126\n          pip install -r requirements-test.txt --progress-bar off\n      - run: TORCH_CUDA_ARCH_LIST=${{ matrix.gpu.sm }} python -m pip install -v --no-build-isolation -e .\n        env:\n          TORCH_DONT_CHECK_COMPILER_ABI: 1\n      - run: python -m xformers.info\n      - name: xFormers import should not init cuda context\n        run: |\n          # NOTE: we check GPU version by default to determine if triton should be used\n          # and this initializes CUDA context, unless we set `XFORMERS_ENABLE_TRITON`\n          XFORMERS_ENABLE_TRITON=1 python -c \"import xformers; import xformers.ops; import torch; assert not torch.cuda.is_initialized()\"\n      - name: Check for PyTorch stable symbols\n        run: |\n          bad_symbols=$(nm --dynamic --undefined-only --demangle xformers/_C.so | grep --extended-regexp \"(torch|at|c10|c10d)::\" || true)\n          if [[ $bad_symbols != \"\" ]]; then echo \"These non-stable PyTorch symbols made it into the xFormers shared library:\"; echo $bad_symbols; exit 1; fi\n      - name: Unit tests\n        run: |\n          python -m pytest --verbose --random-order-bucket=global --maxfail=20 --junitxml=test-results/junit.xml --cov-report=xml --cov=./ tests\n      - name: Publish Test Report\n        uses: mikepenz/action-junit-report@v3\n        if: success() || failure() # always run even if the previous step fails\n        with:\n          report_paths: 'test-results/*.xml'\n"
  },
  {
    "path": ".github/workflows/linters.yml",
    "content": "on:\n  pull_request: {}\n  push:\n    branches:\n      - main\n\njobs:\n  repo:\n    uses: ./.github/workflows/linters_reusable.yml\n"
  },
  {
    "path": ".github/workflows/linters_reusable.yml",
    "content": "name: lint\n\non:\n  workflow_call:\n    inputs:\n      pre-script:\n        type: string\n\njobs:\n  linters:\n    runs-on: ubuntu-22.04\n\n    steps:\n      - uses: actions/checkout@v3\n        with:\n          fetch-depth: 0\n      - name: Setup Python\n        uses: actions/setup-python@v5\n        with:\n          python-version: '3.10'\n      - name: Cleanup host\n        run: |\n          # Github's ubuntu-latest comes with a ton of stuff;\n          # https://carlosbecker.com/posts/github-actions-disk-space suggests\n          # this hotfix:\n          df -h\n          sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL\n          sudo docker image prune --all --force\n          sudo docker builder prune -a\n          df -h\n      - name: Run pre-script\n        if: ${{ inputs.pre-script }}\n        run: ${{ inputs.pre-script }}\n      # Triton is too slow to install, and beside it's not needed\n      - run: sed -i '/triton/d' requirements-test.txt\n      - name: Install deps\n        run: pip install -r requirements-test.txt\n      - name: ufmt\n        if: success() || failure()\n        run: ufmt check\n      - name: mypy\n        if: success() || failure()\n        run: |\n          python -m mypy --version\n          python -m mypy --ignore-missing-imports --scripts-are-modules --pretty --exclude \"(build|stubs|third_party|docs|examples|setup.py)\" .\n      - name: flake8\n        if: success() || failure()\n        run: python -m flake8 --config .flake8 --show-source --statistics\n      - name: clang-format\n        if: success() || failure()\n        run: |\n          pip install clang-format\n          clang-format --version\n\n          # apply to our files - excluding autogenerated files\n          ./.github/run-clang-format.py -e \"*fmha/autogen\" -r xformers/csrc\n      - name: PyTorch stable API includes\n        if: success() || failure()\n        run: |\n          bad_files=$(git grep --extended-regex -e \"#\\s*include\\s*<.*(torch|ATen|c10)\" --and --not -e \"#\\s*include\\s*<torch/(headeronly|csrc/stable)/\" --files-with-matches -- ':(exclude)xformers/csrc/attention/hip_*' || true)\n          if [[ $bad_files != \"\" ]]; then echo \"These files contain non-stable PyTorch includes:\"; echo $bad_files; exit 1; fi\n"
  },
  {
    "path": ".github/workflows/rocm_build.yml",
    "content": "name: rocm-build\n\non:\n  push:\n    branches:\n      - develop\n  pull_request:\n    paths:\n      - \".github/compute_wheel_version.py\"\n      - \".github/workflows/rocm_build.yml\"\n      - \".github/workflows/wheels_build.yml\"\n      - \"setup.py\"\n      - \"requirements*.txt\"\n      - \"xformers/csrc/attention/hip_fmha/**\"\n      - \"third_party/composable_kernel_tiled/**\"\n  workflow_dispatch:\n\njobs:\n  build:\n    strategy:\n      fail-fast: false\n      matrix:\n        os: ['ubuntu-alola']\n        python: ['3.11']\n        torch_version: ['2.10.0']\n        toolkit_type: ['rocm']\n        toolkit_short_version: ['7.0', '7.1']\n\n    uses: ./.github/workflows/wheels_build.yml\n    if: github.repository == 'rocm/xformers'\n    with:\n      os: ${{ matrix.os }}\n      python: ${{ matrix.python }}\n      torch_version: ${{ matrix.torch_version }}\n      toolkit_type: ${{ matrix.toolkit_type }}\n      toolkit_short_version: ${{ matrix.toolkit_short_version }}\n      artifact_tag: ${{ github.run_id }}\n\n  clean:\n    runs-on: 'ubuntu-alola'\n    if: ${{ needs.build.result != 'skipped' }}\n    needs: [build]\n    steps:\n      - name: Remove dangling Docker images\n        run: |\n          docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi\n"
  },
  {
    "path": ".github/workflows/rocm_ci.yml",
    "content": "name: rocm-ci\n\non:\n  pull_request:\n    types: [labeled, synchronize, reopened]\n  workflow_dispatch: {}\n  push:\n    branches:\n      - main\n      - develop\n\njobs:\n  build:\n    if: github.repository == 'rocm/xformers'\n    runs-on: self-hosted-rocm-ci\n    container:\n      image: 'rocm/pytorch-nightly:latest'\n      options: ' --cap-add=SYS_PTRACE --security-opt seccomp=unconfined --device=/dev/kfd --device=/dev/dri --group-add video --ipc=host --shm-size 8G --memory 32G '\n    steps:\n    - uses: actions/checkout@v4\n      with:\n        path: '_xformers'\n        submodules: 'recursive'\n        set-safe-directory: true\n        fetch-depth: 0\n    - name: Get CPU info on Ubuntu\n      if: contains(runner.os, 'linux')\n      run: |\n        cat /proc/cpuinfo\n    - name: Get env vars\n      run: |\n        echo GITHUB_WORKFLOW   = $GITHUB_WORKFLOW\n        echo HOME              = $HOME\n        echo PWD               = $PWD\n        echo GITHUB_ACTION     = $GITHUB_ACTION\n        echo GITHUB_ACTIONS    = $GITHUB_ACTIONS\n        echo GITHUB_REPOSITORY = $GITHUB_REPOSITORY\n        echo GITHUB_EVENT_NAME = $GITHUB_EVENT_NAME\n        echo GITHUB_EVENT_PATH = $GITHUB_EVENT_PATH\n        echo GITHUB_WORKSPACE  = $GITHUB_WORKSPACE\n        echo GITHUB_SHA        = $GITHUB_SHA\n        echo GITHUB_REF        = $GITHUB_REF\n\n        export GIT_BRANCH=${GITHUB_BASE_REF:-${GITHUB_REF#refs/heads/}}\n        echo GIT_BRANCH        = $GIT_BRANCH\n\n        export ROCM_PATH=/opt/rocm\n        echo ROCM_PATH         = $ROCM_PATH\n\n        hipcc --version\n        rocm-smi\n        rocminfo | grep \"gfx\"\n\n    - name: Setup build env\n      run: |\n        conda create -n xformers python=3.11\n        export PATH=/opt/conda/envs/xformers/bin:$PATH\n        python -VV\n\n        python -m pip install -U torch==2.10.0 --index-url=https://download.pytorch.org/whl/rocm7.1\n        python -c \"import torch; print(f'PyTorch version {torch.__version__}')\"\n\n        python -m pip install ninja scipy pytest pytest-html\n\n    - name: Pre-build clean\n      run: |\n        cd _xformers\n        git clean -ffdx\n        cd ..\n\n    - name: Build xformers\n      run: |\n        export PATH=/opt/conda/envs/xformers/bin:$PATH\n        export MAX_JOBS=20\n\n        python -m pip install -e ./_xformers --verbose\n        python -m xformers.info\n\n    - name: Run python tests\n      run: |\n        export PATH=/opt/conda/envs/xformers/bin:$PATH\n\n        python -m pytest --html=test_mem_eff_attention.html --self-contained-html -rpfs ./_xformers/tests/test_mem_eff_attention.py\n\n    - name: Archive logs\n      if: '!cancelled()'\n      uses: actions/upload-artifact@v4\n      with:\n        name: test results\n        path: test_mem_eff_attention.html\n\n    - name: Post-build clean\n      if: '!cancelled()'\n      run: |\n        cd _xformers\n        git clean -ffdx\n        cd ..\n\n  clean:\n    runs-on: self-hosted-rocm-ci\n    if: ${{ needs.build.result != 'skipped' }}\n    needs: [build]\n    steps:\n      - name: Remove dangling Docker images\n        run: |\n          docker images -q -f dangling=true | xargs --no-run-if-empty docker rmi\n"
  },
  {
    "path": ".github/workflows/rocm_docker.yml",
    "content": "name: Build and Publish ROCm Docker Image\n\non:\n  push:\n    branches:\n      - develop\n\njobs:\n  build-and-push:\n    runs-on: rocm\n    if: github.repository == 'rocm/xformers'\n    steps:\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v3\n\n      - name: Login to Docker Hub\n        uses: docker/login-action@v3\n        with:\n          username: ${{ vars.DOCKERHUB_USERNAME }}\n          password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n      - name: Build and push\n        uses: docker/build-push-action@v6\n        with:\n          push: true\n          tags: rocm/xformers:latest\n          file: Dockerfile.rocm\n"
  },
  {
    "path": ".github/workflows/wheels.yml",
    "content": "name: wheels\n\non:\n  pull_request:\n    paths:\n      - \".github/compute_wheel_version.py\"\n      - \".github/workflows/wheel*\"\n      - \".github/actions/setup-build-cuda/action.yml\"\n      - \"setup.py\"\n      - \"requirements*.txt\"\n  push:\n    branches:\n      - main\n    tags:\n      - \"v[0-9]+*\"\n\njobs:\n  target_determinator:\n    runs-on: ubuntu-latest\n    outputs:\n      matrix: ${{ steps.set-matrix.outputs.matrix }}\n    steps:\n    - id: set-matrix\n      shell: python\n      run: |\n        import os\n        import json\n        import itertools\n        environ = os.environ\n\n        # All builds are python-version agnostic,\n        # and built with python 3.10\n        PYTHON_VERSION = \"3.10\"\n        # NOTE: Don't forget to update `upload_pt`'s matrix\n        # when changing the CUDA/ROCM versions below!\n        CU_VERSIONS = ['126', '128', '130']\n        ROCM_VERSIONS = ['7.1']\n\n        include = []\n        for os in ['8-core-ubuntu', 'windows-8-core']:\n          for torch_version in ['2.10.0']:\n            # CUDA builds\n            for cuda_short_version in CU_VERSIONS:\n              if cuda_short_version < \"124\" and \"windows\" in os:\n                print(\"Windows builder no longer compatible with cu<124\")\n                continue\n              include.append(dict(\n                os=os,\n                python=PYTHON_VERSION,\n                torch_version=torch_version,\n                toolkit_type=\"cuda\",\n                toolkit_short_version=cuda_short_version,\n              ))\n              print(include[-1])\n            # ROCM builds\n            for rocm_short_version in ROCM_VERSIONS:\n              if os == 'windows-8-core':\n                continue\n              include.append(dict(\n                os=\"16-core-ubuntu\",  # use for ROCm wheels only to avoid CI timeouts\n                python=PYTHON_VERSION,\n                torch_version=torch_version,\n                toolkit_type=\"rocm\",\n                toolkit_short_version=rocm_short_version,\n              ))\n              print(include[-1])\n        matrix = {'include': include}\n        print(json.dumps(matrix))\n        with open(environ[\"GITHUB_OUTPUT\"], \"a\") as fd:\n          fd.write(\"matrix=\"+json.dumps(matrix))\n  build:\n    needs: target_determinator\n    strategy:\n      fail-fast: false\n      matrix: ${{ fromJson(needs.target_determinator.outputs.matrix) }}\n\n    uses: ./.github/workflows/wheels_build.yml\n    if: github.repository == 'facebookresearch/xformers' || github.event_name == 'pull_request'\n    with:\n      os: ${{ matrix.os }}\n      python: ${{ matrix.python }}\n      torch_version: ${{ matrix.torch_version }}\n      toolkit_type: ${{ matrix.toolkit_type }}\n      toolkit_short_version: ${{ matrix.toolkit_short_version }}\n\n  upload_pip:\n    needs: build\n    uses: ./.github/workflows/wheels_upload_pip.yml\n    with:\n      twine_username: __token__\n      filter: \"*torch2.10.0+cu128*\"\n      execute: ${{ github.repository == 'facebookresearch/xformers' && github.event_name != 'pull_request' }}\n    secrets:\n      twine_password: ${{ secrets.PYPI_TOKEN }}\n\n  upload_pt:\n    needs: build\n    strategy:\n      fail-fast: false\n      matrix:\n        suffix:\n          - cu126\n          - cu128\n          - cu130\n          - rocm7.1\n    uses: ./.github/workflows/wheels_upload_s3.yml\n    with:\n      aws_role: \"arn:aws:iam::749337293305:role/pytorch_bot_uploader_role\"\n      s3_path: s3://pytorch/whl/${{ matrix.suffix }}/\n      aws_s3_cp_extra_args: --acl public-read\n      filter: \"*torch2.10.0+${{ matrix.suffix }}*\"\n      execute: ${{ github.repository == 'facebookresearch/xformers' && github.ref_type == 'tag' }}\n"
  },
  {
    "path": ".github/workflows/wheels_build.yml",
    "content": "name: wheels_build\n\non:\n  workflow_call:\n    inputs:\n      os:\n        required: true\n        type: string\n      python:\n        required: true\n        type: string\n      torch_version:\n        required: true\n        type: string\n        description: \"Example: 1.13.1\"\n      toolkit_type:\n        required: true\n        type: string\n        description: \"Example: cuda for cuda, rocm for rocm\"\n      toolkit_short_version:\n        required: true\n        type: string\n        description: \"Example: 117 for 11.7\"\n      artifact_tag:\n        default: \"facebookresearch\"\n        type: string\n\n# this yaml file can be cleaned up using yaml anchors, but they're not supported in github actions yet\n# https://github.com/actions/runner/issues/1182\n\nenv:\n  # you need at least cuda 5.0 for some of the stuff compiled here.\n  TORCH_CUDA_ARCH_LIST: ${{ contains(inputs.toolkit_type, 'cuda') && '7.5 8.0+PTX' || '' }}\n  HIP_ARCHITECTURES: ${{ contains(inputs.toolkit_type, 'rocm') && 'gfx90a gfx942' || '' }}\n  MAX_JOBS: ${{ contains(inputs.os, 'ubuntu') && '2' || '3' }} # (FA3 is memory hungry!)\n  DISTUTILS_USE_SDK: 1 # otherwise distutils will complain on windows about multiple versions of msvc\n  XFORMERS_BUILD_TYPE: \"Release\"\n  TWINE_USERNAME: __token__\n  XFORMERS_PACKAGE_FROM: \"wheel-${{ github.ref_name }}\"\n\njobs:\n  build:\n    name: ${{ contains(inputs.os, 'ubuntu') && 'ubuntu' || 'win' }}-py${{ inputs.python }}-pt${{ inputs.torch_version }}+${{ contains(inputs.toolkit_type, 'cuda') && 'cu' || 'rocm' }}${{ inputs.toolkit_short_version }}\n    runs-on: ${{ inputs.os }}\n    env:\n      # alias for the current python version\n      # windows does not have per version binary, it is just 'python3'\n      PY: python${{ contains(inputs.os, 'ubuntu') && inputs.python || '3' }}\n\n    container: ${{ contains(inputs.os, 'ubuntu') && 'quay.io/pypa/manylinux_2_28_x86_64' || null }}\n    timeout-minutes: 360\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - if: contains(inputs.toolkit_type, 'cuda') && fromJSON(inputs.toolkit_short_version) >= 120 && fromJSON(inputs.toolkit_short_version) < 130\n        run: |\n          echo \"TORCH_CUDA_ARCH_LIST=$TORCH_CUDA_ARCH_LIST 8.0 9.0a\" >> ${GITHUB_ENV}\n\n      - if: contains(inputs.toolkit_type, 'cuda') && fromJSON(inputs.toolkit_short_version) >= 130\n        run: |\n          echo \"TORCH_CUDA_ARCH_LIST=$TORCH_CUDA_ARCH_LIST 8.0 9.0a 10.0a 10.3a 11.0a 12.0a 12.1a\" >> ${GITHUB_ENV}\n\n      - if: runner.os == 'Windows'\n        run: git config --system core.longpaths true\n      - name: Recursive checkout\n        uses: actions/checkout@v4\n        with:\n          submodules: recursive\n          path: \".\"\n          fetch-depth: 0 # for tags\n\n      - name: HACKFIX for cutlass compiler bug\n        if: runner.os == 'Windows'\n        run: |\n          # See https://github.com/NVIDIA/cutlass/issues/1732\n          rm -f third_party/cutlass/include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_pingpong.hpp\n          touch third_party/cutlass/include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_pingpong.hpp\n      - name: Setup Runner\n        uses: ./.github/actions/setup-build-cuda\n        with:\n          toolkit_type: ${{ inputs.toolkit_type }}\n          toolkit_short_version: ${{ inputs.toolkit_short_version }}\n          python: ${{ inputs.python }}\n      - if: runner.os == 'Linux'\n        run: printenv\n\n      - if: runner.os != 'Windows'\n        name: (Linux) Setup venv for linux\n        shell: bash -l {0}\n        run: |\n          $PY -m venv venv\n          . ./venv/bin/activate\n          which pip\n          echo \"PY=$(which python)\" >> ${GITHUB_ENV}\n          echo \"PATH=$PATH\" >> ${GITHUB_ENV}\n          git config --global --add safe.directory \"*\"\n          pip install packaging ninja wheel setuptools twine\n\n      - name: Define version\n        id: xformers_version\n        env:\n          VERSION_SOURCE: ${{ github.ref_type == 'tag' && 'tag' || 'dev'  }}\n        run: |\n          set -Eeuo pipefail\n          git config --global --add safe.directory \"*\"\n          version=`python .github/compute_wheel_version.py --source $VERSION_SOURCE`\n          echo $version > version.txt\n          echo \"BUILD_VERSION=$version${{ steps.cuda_info.outputs.CUDA_VERSION_SUFFIX }}\" >> ${GITHUB_ENV}\n          echo \"BUILD_VERSION=$version${{ steps.cuda_info.outputs.CUDA_VERSION_SUFFIX }}\" >> ${GITHUB_OUTPUT}\n          which ninja\n          ninja --version\n          cat ${GITHUB_ENV}\n      - run: echo \"xformers-${BUILD_VERSION}\"\n      - run: echo \"release version (will upload to PyTorch)\"\n        if: ${{ !contains(steps.xformers_version.outputs.BUILD_VERSION, '.dev') }}\n\n      - name: Install corresponding PyTorch\n        run: |\n          PYTORCH_INDEX_URL=\"https://download.pytorch.org/whl/${{ contains(inputs.toolkit_type, 'cuda') && 'cu' || 'rocm' }}${{ inputs.toolkit_short_version }}\"\n          $PY -m pip install wheel -r requirements.txt --extra-index-url $PYTORCH_INDEX_URL\n\n      - name: Build wheel\n        shell: bash -l {0}\n        run: |\n          $PY setup.py bdist_wheel -d dist/ -k $PLAT_ARG\n        env:\n          PLAT_ARG: ${{ contains(inputs.os, 'ubuntu') && '--plat-name manylinux_2_28_x86_64' || '' }}\n\n      - run: du -h dist/*\n      - uses: actions/upload-artifact@v4\n        with:\n          name: ${{ inputs.os }}-py${{ inputs.python }}-torch${{ inputs.torch_version }}+${{ contains(inputs.toolkit_type, 'cuda') && 'cu' || 'rocm' }}${{ inputs.toolkit_short_version }}_${{ inputs.artifact_tag }}\n          path: dist/*.whl\n# Note: it might be helpful to have additional steps that test if the built wheels actually work\n"
  },
  {
    "path": ".github/workflows/wheels_upload_pip.yml",
    "content": "name: wheels_upload_pip\n\non:\n  workflow_call:\n    secrets:\n      twine_password:\n        required: true\n    inputs:\n      twine_username:\n        required: true\n        type: string\n      pypirc:\n        required: false\n        type: string\n      filter:\n        required: true\n        type: string\n        description: Filter which runs to upload. Example '*+cu121*'\n      execute:\n        required: true\n        type: boolean\n        description: Actually upload the wheels. Dry-run if false\n      artifact_tag:\n        default: \"facebookresearch\"\n        type: string\n\nenv:\n  TWINE_USERNAME: __token__\n\njobs:\n  wheels_upload_pip:\n    name: wheels_upload_pip\n    runs-on: ubuntu-24.04\n\n    timeout-minutes: 360\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: Recursive checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n          path: \".\"\n          fetch-depth: 0 # for tags\n\n      # inspired by https://github.com/jlumbroso/free-disk-space/blob/main/action.yml\n      - name: Free disk space\n        run: |\n          sudo rm -rf /usr/local/lib/android || true\n          sudo rm -rf /usr/share/dotnet || true\n\n      - name: Setup twine config\n        if: inputs.pypirc\n        run: |\n          echo \"${{ inputs.pypirc }}\" > ~/.pypirc\n          cat ~/.pypirc\n\n      - uses: actions/download-artifact@v4\n        with:\n          path: dist\n\n      # Filter builds (eg vN+cu118 for instance)\n      - run: ls -R dist/\n      - name: Extract builds to upload\n        run: |\n          set -ex\n          mv dist all-dist\n          mkdir dist\n          for f in all-dist/${{ inputs.filter }}_${{ inputs.artifact_tag }}/*.whl; do\n            cp $f dist/\n          done;\n      - run: ls -R dist/\n\n      - name: Setup venv\n        run: |\n          python3 -m venv venv\n          . ./venv/bin/activate\n          which pip\n          # (we need pytorch to create a source distr...)\n          pip install torch packaging twine\n          echo \"PY=$(which python)\" >> ${GITHUB_ENV}\n          echo \"PATH=$PATH\" >> ${GITHUB_ENV}\n\n      - name: Create source distribution\n        env:\n          VERSION_SOURCE: ${{ github.ref_type == 'tag' && 'tag' || 'dev'  }}\n        run: |\n          version=`$PY .github/compute_wheel_version.py --source $VERSION_SOURCE`\n          echo $version > version.txt\n          cat version.txt\n\n          BUILD_VERSION=$version $PY setup.py sdist -d sdist/\n\n      - run: ls -R sdist/\n      - name: Upload wheel to PyPi\n        if: inputs.execute\n        run: $PY -m twine upload --skip-existing dist/*.whl sdist/*\n        env:\n          TWINE_USERNAME: ${{ inputs.twine_username }}\n          TWINE_PASSWORD: ${{ secrets.twine_password }}\n"
  },
  {
    "path": ".github/workflows/wheels_upload_s3.yml",
    "content": "name: wheels_upload_s3\n\non:\n  workflow_call:\n    inputs:\n      aws_role:\n        required: true\n        type: string\n      s3_path:\n        required: true\n        type: string\n        description: Example 's3://bucket/path/xformers/'\n      aws_s3_cp_extra_args:\n        required: false\n        type: string\n        default: ''\n        description: Example '--acl public-read'\n      filter:\n        required: true\n        type: string\n        description: Filter which runs to upload. Example '*+cu121*'\n      execute:\n        required: true\n        type: boolean\n        description: Actually upload the wheels. Dry-run if false\n      artifact_tag:\n        default: \"facebookresearch\"\n        type: string\n\njobs:\n  wheels_upload_s3:\n    permissions:\n      id-token: write # Needed to assume AWS role\n      pull-requests: read\n      contents: read\n    name: ${{ inputs.s3_path }}\n    runs-on: ubuntu-24.04\n\n    timeout-minutes: 360\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: Recursive checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n          path: \".\"\n          fetch-depth: 0 # for tags\n\n      # inspired by https://github.com/jlumbroso/free-disk-space/blob/main/action.yml\n      - name: Free disk space\n        run: |\n          sudo rm -rf /usr/local/lib/android || true\n          sudo rm -rf /usr/share/dotnet || true\n\n      - uses: actions/download-artifact@v4\n        with:\n          path: dist\n      # Filter builds (eg vN+cu118 for instance)\n      - run: ls -R dist/\n      - name: Extract builds to upload\n        run: |\n          set -ex\n          mv dist all-dist\n          mkdir dist\n          for f in all-dist/${{ inputs.filter }}_${{ inputs.artifact_tag }}/*.whl; do\n            cp $f dist/\n          done;\n      - run: ls -R dist/\n\n      - name: configure aws credentials\n        if: inputs.execute\n        uses: aws-actions/configure-aws-credentials@v1.7.0\n        with:\n          role-to-assume: ${{ inputs.aws_role }}\n          role-session-name: GitHub_CI\n          aws-region: \"us-east-1\"\n\n      - name: Sts GetCallerIdentity\n        if: inputs.execute\n        run: |\n          aws sts get-caller-identity\n\n      - name: Upload wheels to ${{ inputs.s3_path }}\n        if: inputs.execute\n        run: |\n          set -ex\n          for f in dist/*.whl; do\n            echo $f;\n            aws s3 cp $f ${{ inputs.s3_path }} ${{ inputs.aws_s3_cp_extra_args }}\n          done;\n          aws s3 ls ${{ inputs.s3_path }}\n"
  },
  {
    "path": ".github/workflows/win-build.yml",
    "content": "name: win-build\n\non:\n  pull_request:\n    paths:\n      - \"third_party/**\"\n      - \"xformers/csrc/**\"\n      - \".github/workflows/win-build.yml\"\n      - \".github/actions/setup-build-cuda/action.yml\"\n      - \"setup.py\"\n      - \"requirements*.txt\"\n\nenv:\n  FORCE_CUDA: 1\n  MAX_JOBS: 6\n  DISTUTILS_USE_SDK: 1 # otherwise distutils will complain on windows about multiple versions of msvc\n  XFORMERS_BUILD_TYPE: \"Release\"\n  TMPDIR: \"./x\"\n\njobs:\n  win_build:\n    strategy:\n      fail-fast: false\n      matrix:\n        arch:\n          - \"8.0\"\n    name: win-build-${{ matrix.arch }}\n    runs-on: windows-8-core\n    env:\n      PY: python3\n      TORCH_CUDA_ARCH_LIST: ${{ matrix.arch }}\n\n    timeout-minutes: 360\n    defaults:\n      run:\n        shell: bash\n    steps:\n      - name: Workarounds for longpaths - git-config\n        run: |\n          git config --system core.longpaths true\n      - name: Recursive checkout\n        uses: actions/checkout@v3\n        with:\n          submodules: recursive\n          path: \".\"\n\n      - name: Workarounds for longpaths - TMPDIR\n        run: |\n          mkdir x\n          python -c \"import tempfile; print(tempfile.gettempdir())\"\n          python -c \"import tempfile; assert(len(tempfile.gettempdir()) < 30)\"\n\n      - name: HACKFIX for cutlass compiler bug\n        if: runner.os == 'Windows'\n        run: |\n          # See https://github.com/NVIDIA/cutlass/issues/1732\n          rm -f third_party/cutlass/include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_pingpong.hpp\n          touch third_party/cutlass/include/cutlass/gemm/kernel/sm90_gemm_tma_warpspecialized_pingpong.hpp\n\n      - name: Setup Runner\n        uses: ./.github/actions/setup-build-cuda\n        with:\n          toolkit_type: \"cuda\"\n          toolkit_short_version: \"130\"\n          python: \"3.10\"\n\n      - name: Remove internal code\n        run: |\n          mkdir -p .github/sync.fairinternal/\n          touch .github/sync.fairinternal/ossify.sh\n          chmod +x .github/sync.fairinternal/ossify.sh\n          .github/sync.fairinternal/ossify.sh\n\n      - name: Install build dependencies\n        run: |\n          $PY -m pip install wheel setuptools ninja -r requirements.txt --extra-index-url https://download.pytorch.org/whl/cu130\n          git config --global --add safe.directory \"*\"\n          $PY -c \"import torch; print('torch', torch.__version__)\"\n          $PY -c \"import torch; print('torch.cuda', torch.version.cuda)\"\n          ninja --version\n\n      - name: Create sdist\n        run: $PY setup.py sdist\n\n      - name: Build from sdist\n        shell: bash -l {0}\n        run: |\n          $PY -m pip install -v --no-build-isolation dist/*\n\n      - name: Info\n        run: |\n          cd ../../  # So we don't have a folder named `xformers`\n          XFORMERS_MORE_DETAILS=1 $PY -m xformers.info\n\n      # - name: Open an SSH session on failure to debug\n      #   if: ${{ failure() }}\n      #   uses: mxschmitt/action-tmate@v3\n"
  },
  {
    "path": ".gitignore",
    "content": "*~\n*.swp\n\n*.pyc\n*.pyo\n*.so\n\n.mypy_cache/\n*.egg-info/\n\nbuild/\ndist/\n\n# for autocomplete\ncompile_commands.json\n\n# Pytest verbose output\ntest-results/\n\n# Coverage reports\n.coverage\n.coverage.*\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n.vscode/*\nxformers/benchmarks/LRA/datasets\nxformers/benchmarks/LRA/logs\n\nmy_runs.md\n\n# Triton cache\n.cache\n\n# JetBrains PyCharm IDE\n.idea/\n\n# Pyre cache\n.pyre/\n\n# Watchman config files\n.watchmanconfig\n\n# examples demo files\nexamples/input.txt\nexamples/lightning_logs\nexamples/data\n\n# Hydra default output dir\nmultirun\noutputs\n\n.benchmarks\nxformers/version.py\nxformers/cpp_lib.json\n\n## temporary files\nxformers/csrc/attention/hip_fmha/*.cu\nxformers/csrc/attention/hip_fmha/*.hip\nxformers/csrc/attention/hip_fmha/*_hip.h\nxformers/csrc/attention/hip_fmha/instances/*.cu\nxformers/csrc/attention/hip_fmha/instances/*.hip\nxformers/csrc/attention/hip_fmha/instances/*_hip.h\nxformers/csrc/attention/hip_decoder/*.cu\nxformers/csrc/attention/hip_decoder/*.hip\nxformers/csrc/attention/hip_decoder/*_hip.h\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"third_party/cutlass\"]\n\tpath = third_party/cutlass\n\turl = https://github.com/NVIDIA/cutlass.git\n[submodule \"third_party/composable_kernel_tiled\"]\n\tpath = third_party/composable_kernel_tiled\n\turl = https://github.com/ROCm/composable_kernel.git\n\tbranch = develop\n"
  },
  {
    "path": ".isort.cfg",
    "content": "[settings]\nknown_third_party =fvcore,hydra,input_pipeline,matplotlib,numpy,omegaconf,pandas,pl_bolts,pyre_extensions,pytest,pytorch_lightning,ragged_inference,recommonmark,seaborn,setuptools,sklearn,submitit,tensorflow,timm,torch,torchmetrics,torchvision,tqdm,triton,typing_extensions\nskip_glob=third_party/*\n"
  },
  {
    "path": ".markdownlint.json",
    "content": "{\n    \"MD013\": false,\n    \"MD033\": false\n}\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "exclude: 'build|stubs'\n\ndefault_language_version:\n    python: python3\n\nrepos:\n-   repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v3.4.0\n    hooks:\n    -   id: trailing-whitespace\n    -   id: check-ast\n    -   id: check-merge-conflict\n    -   id: no-commit-to-branch\n        args: ['--branch=master']\n    -   id: check-added-large-files\n        args: ['--maxkb=500']\n    -   id: end-of-file-fixer\n\n- repo: https://github.com/omnilib/ufmt\n  rev: v2.8.0\n  hooks:\n    - id: ufmt\n      additional_dependencies:\n        - black == 26.3.1\n        - usort == 1.0.8.post1\n\n-   repo: https://github.com/pycqa/flake8\n    rev: 6.1.0\n    hooks:\n    -   id: flake8\n        additional_dependencies: [flake8-copyright]\n\n-   repo: https://github.com/pre-commit/mirrors-mypy\n    rev: 'v1.10.0'\n    hooks:\n    -   id: mypy\n"
  },
  {
    "path": ".pyre_configuration",
    "content": "{\n  \"ignore_all_errors\": [\"xformers/benchmarks/\"],\n  \"python_version\": \"3.9\",\n  \"source_directories\": [\n    \"stubs\",\n    {\"import_root\": \".\", \"source\": \"xformers\"}\n  ]\n}\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [0.0.36] - 2026-??-??\n\n\n## [0.0.35] - 2026-02-20\nPre-built binary wheels are available for PyTorch 2.10.0 (and later).\n\n### Improved\n- Supported free-threading Python.\n\n### Removed\n- Stopped bundling pre-built versions of Flash-Attention 3, and instead started relying on the wheels provided by the PyTorch indices.\n\n\n## [0.0.34] - 2026-01-23\nPre-built binary wheels are available for PyTorch 2.10.0 (and later).\n\n### Improved\n- Migrated xFormers to the PyTorch stable API/ABI, which means that binary builds targeting PyTorch 2.10+ will be compatible with any later version\n\n### Removed\n- Removed optimized fast-path of SwiGLU (which was only available for A100 GPUs)\n- Removed most legacy components\n\n\n## [0.0.33.post2] - 2025-12-03\nPre-built binary wheels are available for PyTorch 2.9.1.\n\n\n## [0.0.33.post1] - 2025-11-13\nFixed issues with wheel upload to PyPI\n\n\n## [0.0.33] - 2025-11-12\nPre-built binary wheels are available for PyTorch 2.9.0.\n\n### Added\n- cutlass fmha Op for Blackwell GPUs\n- Support flash-attention package up to 2.8.3\n- expose FA3 deterministic mode\n- FW+BW pass overlap for DeepSeek-like comms/compute overlap\n\n### Improved\n- merge_attentions support for irregular head dimension\n\n\n## [0.0.32] - 2025-08-13\nPre-built binary wheels are available for PyTorch 2.8.0.\n\n### Added\n- Support flash-attention package up to 2.8.2\n- Speed improvements to `python -m xformers.profiler.find_slowest`\n\n### Removed\n- Removed autograd backward pass for merge_attentions as it is easy to use incorrectly.\n- Attention biases are no longer `torch.Tensor` subclasses. This is no longer\nnecessary for torch.compile to work, and was adding more complexity\n\n\n## [0.0.31] - 2025-06-25\nPre-built binary wheels are available for PyTorch 2.7.1.\n### Added\n- xFormers wheels are now python-version agnostic: this means that the same wheel can be used for python 3.9, 3.10, ... 3.13\n- Added support for Flash-Attention 3 on Ampere GPUs\n### Removed\n- We will no longer support V100 or older GPUs, following PyTorch (pytorch/pytorch#147607)\n- Deprecated support for building Flash-Attention 2 as part of xFormers. For Ampere GPUs, we now use Flash-Attention 3 on windows, and Flash-Attention 2 can still be used through PyTorch on linux.\n\n## [0.0.30] - 2025-04-28\nPre-built binary wheels are available for PyTorch 2.7.0. Following PyTorch, we build wheels for CUDA 11.8, 12.6, and 12.8 only (we no longer build for CUDA 12.4).\nxFormers now requires PyTorch >= 2.7\n### Added\n- [fMHA] Added support for local attention on the Flash3 backend (H100)\n- [fMHA] Added a new paged gappy attention bias\n### Improved\n- [fMHA] The FlashAttention3 backend now ships with more head dimensions to support MLA, and with a FLOPs formula in order to be compatible with PyTorch's partitioner-base automatic activation checkpointing\n- The fused operators for sequence parallelism were migrated to PyTorch's SymmetricMemory\n- The profiler prepends the traces' filenames with the rank of the process when doing distributed training\n### Removed\n- Removed documentation for legacy unmaintained components\n\n## [0.0.29.post2] - 2025-01-31\nPre-built binary wheels are available for PyTorch 2.6.0. Following PyTorch, we build wheels for CUDA 11.8, 12.4, and 12.6 only (we no longer build for CUDA 12.1).\nxFormers now requires PyTorch >= 2.6\n\n\n## [0.0.29] - 2024-12-27\n### Improved:\n- [fMHA] Creating a `LowerTriangularMask` no longer creates a CUDA tensor\n- [fMHA] Updated Flash-Attention to `v2.7.2.post1`\n- [fMHA] Flash-Attention v3 will now be used by `memory_efficient_attention` by default when available, unless the operator is enforced with the `op` keyword-argument. Switching from Flash2 to Flash3 can make transformer trainings ~10% faster end-to-end on H100s\n- [fMHA] Fixed a performance regression with the `cutlass` backend for the backward pass (facebookresearch/xformers#1176) - mostly used on older GPUs (eg V100)\n- Fixed swiglu operator compatibility with torch-compile with PyTorch 2.6\n- Fix activation checkpointing of SwiGLU when AMP is enabled (facebookresearch/xformers#1152)\n### Removed:\n- Following PyTorch, xFormers no longer builds binaries for conda. Pip is now the only recommended way to get xFormers\n- Removed unmaintained/deprecated components in `xformers.components.*` (see facebookresearch/xformers#848)\n\n## [0.0.28.post3] - 2024-10-30\nPre-built binary wheels require PyTorch 2.5.1\n\n## [0.0.28.post2] - 2024-10-18\nPre-built binary wheels require PyTorch 2.5.0\n\n## [0.0.28.post1] - 2024-09-13\nProperly upload wheels for cuda 12.4\n\n## [0.0.28] - 2024-09-12\nPre-built binary wheels require PyTorch 2.4.1\n### Added\n- Added wheels for cuda 12.4\n- Added conda builds for python 3.11\n- Added wheels for rocm 6.1\n### Improved\n- Profiler: Fix computation of FLOPS for the attention when using xFormers\n- Profiler: Fix MFU/HFU calculation when multiple dtypes are used\n- Profiler: Trace analysis to compute MFU & HFU is now much faster\n- fMHA/splitK: Fixed `nan` in the output when using a `torch.Tensor` bias where a lot of consecutive keys are masked with `-inf`\n- Update Flash-Attention version to `v2.6.3` *when building from scratch*\n- When using the most recent version of Flash-Attention, it is no longer possible to mix it with the cutlass backend. In other words, it is no longer possible to use the cutlass Fw with the flash Bw.\n### Removed\n- fMHA: Removed `decoder` and `small_k` backends\n- profiler: Removed `DetectSlowOpsProfiler` profiler\n- Removed compatibility with PyTorch < 2.4\n- Removed conda builds for python 3.11\n- Removed windows pip wheels for cuda 12.1 and 11.8\n\n## [0.0.27.post2] - 2024-07-26\nPre-built binary wheels require PyTorch 2.4.0\n\n## [0.0.27.post1] - 2024-07-25\nPre-built binary wheels require PyTorch 2.4.0\n\n## [0.0.27] - 2024-07-10\nPre-built binary wheels require PyTorch 2.3.1\n### Added\n- fMHA: `PagedBlockDiagonalGappyKeysMask`\n- fMHA: heterogeneous queries in `triton_splitk`\n- fMHA: support for paged attention in flash\n- fMHA: Added backwards pass for `merge_attentions`\n- fMHA: Added `torch.compile` support for 3 biases (`LowerTriangularMask`, `LowerTriangularMaskWithTensorBias` and `BlockDiagonalMask`) - some might require PyTorch 2.4\n- fMHA: Added `torch.compile` support in `memory_efficient_attention` when passing the flash operator explicitely (eg `memory_efficient_attention(..., op=(flash.FwOp, flash.BwOp))`)\n- fMHA: `memory_efficient_attention` now expects its `attn_bias` argument to be on the same device as the other input tensor. Previously, it would convert the bias to the right device.\n- fMHA: `AttentionBias` subclasses are now constructed by default on the `cuda` device if available - they used to be created on the CPU device\n- 2:4 sparsity: Added `xformers.ops.sp24.sparsify24_ste` for Straight Through Estimator (STE) with options to rescale the gradient differently for masked out/kept values\n### Improved\n- fMHA: Fixed out-of-bounds reading for Split-K triton implementation\n- Profiler: fix bug with modules that take a single tuple as argument\n- Profiler: Added manual trigger for a profiling step, by creating a `trigger` file in the profiling directory\n### Removed\n- Removed support for PyTorch version older than 2.2\n\n## [0.0.26] - 2024-04-29\nPre-built binary wheels require PyTorch 2.3.0\n### Added\n- [2:4 sparsity] Added support for Straight-Through Estimator for `sparsify24` gradient (`GRADIENT_STE`)\n- [2:4 sparsity] `sparsify24_like` now supports the cuSparseLt backend, and the STE gradient\n- Basic support for `torch.compile` for the `memory_efficient_attention` operator. Currently only supports Flash-Attention, and without any bias provided. We want to expand this coverage progressively.\n### Improved\n- merge_attentions no longer needs inputs to be stacked.\n- fMHA: triton_splitk now supports additive bias\n- fMHA: benchmark cleanup\n\n## [0.0.25.post1] - 2024-03-29\nPre-built binary wheels require PyTorch 2.2.2\n\n## [0.0.25] - 2024-03-14\nPre-built binary wheels require PyTorch 2.2.1\n### Added\n- New `merge_attentions` function\n- fMHA: New gappy attention biases.\n### Improved\n- fMHA: Updated Flash-Attention to v2.5.6: this has a performance improvement for multiquery.\n- fMHA: triton_splitk changed and expanded. Now amalgamates using LSE. Can autotune, supports causal with a small number of queries - not just 1. Experimental support for paged attention.\n- `rope_padded`: Fixed CUDA error with many queries (more than 65k)\n- `rmsnorm`: Fixed CUDA error with large inputs (enables 512k+ sequence length on Llama2 70B)\n### Removed\n- fMHA: Removed triton operator (`fmha.triton.*`, `xformers.ops.MemoryEfficientAttentionTritonFwdFlashBwOp`, `xformers.ops.TritonFlashAttentionOp`), as it has correctness issues under some conditions, and is slower than other implementations.\n\n## [0.0.24] - 2024-01-31\nPre-built binary wheels require PyTorch 2.2.0\n### Added\n- Added components for model/sequence parallelism, as near-drop-in replacements for FairScale/Megatron Column&RowParallelLinear modules. They support fusing communication and computation for sequence parallelism, thus making the communication effectively free. [Read more](https://twitter.com/d_haziza/status/1753030654118211593)\n- Added kernels for training models with 2:4-sparsity. We introduced a very fast kernel for converting a matrix A into 24-sparse format, which can be used during training to sparsify weights dynamically, activations etc... xFormers also provides an API that is compatible with torch-compile, see `xformers.ops.sparsify24`.\n### Improved\n- Make selective activation checkpointing be compatible with torch.compile.\n### Removed\n- Triton kernels now require a GPU with compute capability 8.0 at least (A100 or newer). This is due to newer versions of triton not supporting older GPUs correctly\n- Removed support for PyTorch version older than 2.1.0\n\n## [0.0.23] - 2023-12-05\nPre-built binary wheels require PyTorch 2.1.1 (xFormers `0.0.23`) or PyTorch 2.1.2 (xFormers `0.0.23.post1`).\n### Fixed\n- fMHA: Fixed a bug in cutlass backend forward pass where the logsumexp was not correctly calculated, resulting in wrong results in the BW pass. This would happen with MQA when one sequence has a query with `length%64 == 1`\n- fMHA: Updated Flash-Attention to v2.3.6 - this fixes a performance regression in causal backward passes, and now supports `BlockDiagonalCausalWithOffsetPaddedKeysMask`\n### Added\n- fMHA: Added `LocalAttentionFromBottomRightMask` (local)\n- fMHA: Added `LowerTriangularFromBottomRightMask` (causal)\n- fMHA: Added `LowerTriangularFromBottomRightLocalAttentionMask` (local + causal)\n### Removed\n- Removed `xformers.triton.sum_strided`\n\n## [0.0.22] - 2023-09-27\n### Fixed\n- fMHA: Backward pass now works in PyTorch deterministic mode (although slower)\n### Added\n- fMHA: Added experimental support for Multi-Query Attention and Grouped-Query Attention. This is handled by passing 5-dimensional inputs to `memory_efficient_attention`, see the documentation for more details\n- fMHA: Added experimental support for Local Attention biases to `memory_efficient_attention`\n- Added an example of efficient [LLaMa decoding](https://github.com/facebookresearch/xformers/tree/main/examples/llama_inference) using xformers operators\n- Added Flash-Decoding for faster attention during Large Language Model (LLM) decoding - up to 50x faster for long sequences (token decoding up to 8x faster end-to-end)\n- Added an efficient rope implementation in triton, to be used in LLM decoding\n- Added selective activation checkpointing, which gives fine-grained control of which activations to keep and which activations to recompute\n- `xformers.info` now indicates the Flash-Attention version used\n### Removed\n- fMHA: Removed `smallK` backend support for CPU. `memory_efficient_attention` only works for CUDA/GPU tensors now\n- **DEPRECATION**: Many classes in `xformers.factory`, `xformers.triton` and `xformers.components` have been or will be deprecated soon (see tracking issue facebookresearch/xformers#848)\n\n## [0.0.21] - 2023-08-18\n### Improved\n- fMHA: Updated [flash-attention](https://github.com/Dao-AILab/flash-attention) to v2, with massive performance improvements for both the forward pass and backward pass. This implementation is now used by default when it's available\n### Bug fixes\n- fMHA/cutlass: Fix potential race condition in the FW/BW passes\n- fMHA/cutlass: Fix `attn_bias` stride overflow for very long sequences (>32k)\n- `LowerTriangularMask` is now backward compatible with older xformers versions\n### Breaking changes\n- `memory_efficient_attention` now expects the `attn_bias` argument to have a head dimension\n- `memory_efficient_attention` no longer broadcasts the batch/head dimensions of `attn_bias`. Please use `.expand` if you need to broadcast the bias\n- Remove `causal_diagonal` argument from `BlockDiagonalCausalWithOffsetPaddedKeysMask`\n### Added\n- Binary wheels on pypi/conda now contain H100 kernels\n- fMHA: Added backend specialized for decoding that does not use TensorCores - useful when not using multiquery\n\n**NOTE**: Binary wheels are now provided only for PyTorch 2 with cuda 11.8. It is still possible to use xFormers with older versions of PyTorch by building from source or using conda.\n\n\n## [0.0.20] - 2023-05-23\n### Improved\n- fMHA/cutlass (backward): Massive performance improvements when `batch_size * num_heads` is low (10x+)\n- fMHA/cutlass: Further performance improvements for both the forward & backward kernels\n- fMHA (backward): Now dispatching to cutlass when `embed_dim>64`\n- fMHA: Updated Flash-Attention to `v1.0.5`\n### Added\n- fMHA now runs on H100 (support is experimental)\n\n## [0.0.19] - 2023-04-28\n### Added\n- Display `nvcc` version used to compile `xformers` in `python -m xformers.info`\n\n### Fixed\n- Fixed performance regression with `nvcc>11.6` (facebookresearch/xformers#712)\n- fMHA/cutlass: Fixed `nan` in the output when using a `torch.Tensor` with `-inf` prefixes as `attn_bias` (facebookresearch/xformers#722)\n- fMHA/cutlass: Fixed `nan` in the output when the sequence length is larger than `2 ** 15` (facebookresearch/xformers#719)\n- fMHA/cutlass: Significative performance improvements (up to 2x) for both the forward pass and backward pass\n- fMHA/cutlass: The kernel are now deterministic\n- fMHA/cutlass: Fixed backward pass correctness when using dropout (facebookresearch/xformers#724)\n\n## [0.0.18] - 2023-03-31\n### Added\n- Added `xformers.ops.index_select_cat` and `xformers.ops.scaled_index_add` - those are experimental functions that only work with a few shapes, and can be used to write efficient stochastic depth in transformer architectures for instance\n\n### Fixed\n- fMHA: `memory_efficient_attention` now accepts `torch.Tensor` as attention bias for any seqlen, although there are still requirements on the alignment of the bias tensor (see facebookresearch/xformers#683)\n\n## [0.0.17] - 2023-03-28\n### Fixed\n- fMHA: Fixed BW pass on Sm86/Sm89 GPUs when `K > 64` (RTX 3090, RTX 4090, A6000, ..) [facebookresearch/xformers#631]\n\n### Added\n- fMHA/CUTLASS: Added tensor attn bias support [facebookresearch/xformers#587] - contribution from [@jfc4050](https://github.com/jfc4050)\n- fMHA/CUTLASS: Added tensor attn bias grad support [facebookresearch/xformers#587] - contribution from [@jfc4050](https://github.com/jfc4050)\n- fMHA/CUTLASS: Added dropout support [facebookresearch/xformers#587] - contribution from [@jfc4050](https://github.com/jfc4050)\n- fMHA: Added support for varying sequence lengths [facebookresearch/xformers#500]\n\n\n## [0.0.16] - 2023-01-31\n### Fixed\n- Updated triton dependency [facebookresearch/xformers#418]\n- Stripe lineinfo from binaries, reducing the binary size [facebookresearch/xformers#549]\n- Added support for pip wheels [facebookresearch/xformers#588, facebookresearch/xformers#573, facebookresearch/xformers#534, facebookresearch/xformers#523, ...] big thanks to [@AbdBarho](https://github.com/AbdBarho)!\n- Fixed compatibility with Python 3.7 [facebookresearch/xformers#541] - thanks to [@susumuota](https://github.com/susumuota)\n- fMHA: Fixed strides for QKV gradients for cutlass attention [facebookresearch/xformers#535]\n- fMHA: Stricter inputs validation to avoid CUDA errors for unsupported inputs [facebookresearch/xformers#592]\n- fMHA/Flash-Attention: Updated to https://github.com/HazyResearch/flash-attention/commit/a1f49a2b92b6fa022379bbebafed9d7f5e96a675 with multiple changes from [@TriDao](https://github.com/tridao) that make the operator up to 20% faster\n- fMHA/Flash-Attention: Fixed backward pass wrapper, where non-contiguous gradients could give the wrong result [facebookresearch/xformers#548]\n- fMHA: Separate each operator into forward and backward operators. It's now possible to use any combination of forward+backward (for instance Triton forward and Flash-Attention backward) [facebookresearch/xformers#560]\n\n### Added\n- fMHA: Added Triton operator for forward pass from [Flash-Attention](https://github.com/HazyResearch/flash-attention/blob/main/flash_attn/flash_attn_triton.py) authored by [@TriDao](https://github.com/tridao), will be automatically used on A100 when compatible\n- fMHA: Added [`xformers.ops.memory_efficient_attention_forward`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention_forward), [`xformers.ops.memory_efficient_attention_forward_requires_grad`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention_forward_requires_grad), [`xformers.ops.memory_efficient_attention_backward`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention_backward) for power-users who write custom autograd functions [facebookresearch/xformers#560]\n- fMHA: Support for custom scaling for the CUTLASS-based kernel [facebookresearch/xformers#530] - contribution from [@comaniac](https://github.com/comaniac)\n\n## [0.0.15] - Skipped\n\n## [0.0.14] - 2022-11-10\n### Fixed\n- fMHA/CUTLASS: The current CUDA stream is now used by the kernel [facebookresearch/xformers#491]\n- fMHA/CUTLASS: Improve overall performance\n\n### Added\n- SwiGLU: Added `xformers.ops.SwiGLU` and its functional counterpart (`xformers.ops.swiglu`) [facebookresearch/xformers#490]\n- fMHA: Possible to combine CUTLASS's forward with flash-attention's backward pass [facebookresearch/xformers#469] - improves performance on A100 for K = 128\n- fMHA: Add custom `xformers.ops.unbind` operator to avoid a cat in the attention block [facebookresearch/xformers#458]\n\n## [0.0.13] - 2022-09-26\n### Added\n- fMHA: Added CUTLASS-based kernel for `xformers.ops.memory_efficient_attention`. This kernel is automatically depending on the inputs, and works on any GPU after P100 [facebookresearch/xformers#362]\n\n## [0.0.12] - 2022-08-08\n### Fixed\n- Removed duplicated biases in the FusedMLP layers [facebookresearch/xformers#317]\n- Rotary embeddings respecting input types [facebookresearch/xformers#326]\n- Poolformer style instantiating useless projection layers [facebookresearch/xformers#349]\n- Fix layer position not being properly tracked, causing extra layernorms for programmatic xformers [facebookresearch/xformers#348]\n- Pass use_triton flag to LayerNorm module [facebookresearch/xformers#336]\n\n### Added\n- Four blocksparsity layouts from DeepSpeed [facebookresearch/xformers#320]\n- Support several initialization options [facebookresearch/xformers#312]\n- Conv2DFeedforward feedforward part [facebookresearch/xformers#321]\n- VisualAttention [facebookresearch/xformers#329]\n- Automatic blocksparse for causal attention [facebookresearch/xformers#334]\n- Better hierarchical transformer generation [facebookresearch/xformers#345]\n- Fused operations with AOTAutograd/NVFuser, integration into MLP [facebookresearch/xformers#357]\n- Refactor LRA code to use Pytorch Lightning [facebookresearch/xformers#343]\n\n## [0.0.11] - 2022-05-30\n### Fixed\n- Fix some torchscriptability [facebookresearch/xformers#246]\n- Fix FourierMix being compatible with AMP [facebookresearch/xformers#258]\n- Better asserts on QKV dimensions [facebookresearch/xformers#264]\n- Better perfs for FusedMLP and FusedLinearLayer [facebookresearch/xformers#283]\n- Deepnorm init missing self-attention [facebookresearch/xformers#284]\n\n### Added\n- Simplicial Embeddings [facebookresearch/xformers#259]\n- Mem efficient attention, FW pass [facebookresearch/xformers#267]\n- MHA benchmark\n- MLP benchmark\n- Move all triton kernels to triton v2 [facebookresearch/xformers#272]\n- Mem efficient attention, BW pass [facebookresearch/xformers#281]\n- Metaformer support [facebookresearch/xformers#294]\n\n## [0.0.10] - 2022-03-14\n### Fixed\n- Expose bias flag for feedforwards, same default as Timm [facebookresearch/xformers#220]\n- Update eps value for layernorm, same default as torch [facebookresearch/xformers#221]\n- PreNorm bugfix, only one input was normalized [facebookresearch/xformers#233]\n- Fix bug where embedding dimensions that did not match model dim would lead to a crash [facebookresearch/xformers#244]\n\n### Added\n- Add DeepNet (DeepNorm) residual path and init [facebookresearch/xformers#227]\n\n## [0.0.9] - 2022-02-09\n### Added\n- Compositional Attention [facebookresearch/xformers#41]\n- Experimental Ragged attention [facebookresearch/xformers#189]\n- Mixture of Experts [facebookresearch/xformers#181]\n- BlockSparseTensor [facebookresearch/xformers#202]\n- Nd-tensor support for triton softmax [facebookresearch/xformers#210]\n\n### Fixed\n- Bugfix Favor, single feature map [facebookresearch/xformers#183]\n- Sanity check blocksparse settings [facebookresearch/xformers#207]\n- Fixed some picklability [facebookresearch/xformers#204]\n\n## [0.0.8] - 2022-01-07\n### Fixed\n- Much faster fused dropout [facebookresearch/xformers#164]\n- Fused dropout repeatability [facebookresearch/xformers#173]\n\n### Added\n- Embedding weight tying option [facebookresearch/xformers#172]\n\n## [0.0.7] - 2021-11-30\n### Fixed\n- Dropout setting not properly passed in many attentions [facebookresearch/xformers#123]\n\n## [0.0.6] - 2021-11-24\n### Fixed\n- Fix self attention optimization not being triggered, broken residual path [facebookresearch/xformers#119]\n- Improve speed by not using contiguous Tensors when not needed [facebookresearch/xformers#119]\n\n### Added\n- Attention mask wrapper [facebookresearch/xformers#113]\n- ViT comparison benchmark [facebookresearch/xformers#117]\n\n## [0.0.4] - 2021-11-16\n### Fixed\n- Homogenizing the masks, additive or bool [facebookresearch/xformers#79][facebookresearch/xformers#85][facebookresearch/xformers#86]\n- Fix causality flag not being respected [facebookresearch/xformers#103]\n- Enabling FusedLayerNorm by default in the factory if Triton is available\n- Fixing Favor with fp16\n- Fixing Favor trainability\n\n### Added\n- Fused dropout/bias/activation layer [facebookresearch/xformers#58]\n- Fused layernorm used by default in the factory [facebookresearch/xformers#92]\n\n\n## [0.0.3] - 2021-11-01\n### Fixed\n- Nystrom causal attention [facebookresearch/xformers#75]\n\n\n## [0.0.2] - 2021-11-01\n### Fixed\n- More robust blocksparse [facebookresearch/xformers#24]\n\n### Added\n- Rotary embeddings [facebookresearch/xformers#32]\n- More flexible layernorm [facebookresearch/xformers#50]\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as\ncontributors and maintainers pledge to make participation in our project and\nour community a harassment-free experience for everyone, regardless of age, body\nsize, disability, ethnicity, sex characteristics, gender identity and expression,\nlevel of experience, education, socio-economic status, nationality, personal\nappearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment\ninclude:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or\nadvances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic\naddress, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\nprofessional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies within all project spaces, and it also applies when\nan individual is representing the project or its community in public spaces.\nExamples of representing a project or community include using an official\nproject e-mail address, posting via an official social media account, or acting\nas an appointed representative at an online or offline event. Representation of\na project may be further defined and clarified by project maintainers.\n\nThis Code of Conduct also applies outside the project spaces when there is a\nreasonable belief that an individual's behavior may have a negative impact on\nthe project or its community.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported by contacting the project team at <opensource-conduct@fb.com>. All\ncomplaints will be reviewed and investigated and will result in a response that\nis deemed necessary and appropriate to the circumstances. The project team is\nobligated to maintain confidentiality with regard to the reporter of an incident.\nFurther details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good\nfaith may face temporary or permanent repercussions as determined by other\nmembers of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,\navailable at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html\n\n[homepage]: https://www.contributor-covenant.org\n\nFor answers to common questions about this code of conduct, see\nhttps://www.contributor-covenant.org/faq\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to the xFormers repo\n\nWe want to make contributing to this project as easy and transparent as\npossible.\n\n## Our Development Process\n\nMinor changes and improvements will be released on an ongoing basis. Larger\nchanges (e.g., changesets implementing a new paper) will be released on a\nmore periodic basis.\n\n## Pull Requests\n\nWe actively welcome your pull requests.\n\n1. Fork the repo and create your branch from `main`.\n2. If you've added code that should be tested, add tests.\n3. If you've changed APIs, update the documentation.\n4. Ensure the test suite passes.\n5. Make sure your code lints.\n6. If you haven't already, complete the Contributor License Agreement (\"CLA\").\n\n## Contributor License Agreement (\"CLA\")\n\nIn order to accept your pull request, we need you to submit a CLA. You only need\nto do this once to work on any of Facebook's open source projects.\n\nComplete your CLA here: <https://code.facebook.com/cla>\n\n## Issues\n\nWe use GitHub issues to track public bugs. Please ensure your description is\nclear and has sufficient instructions to be able to reproduce the issue.\n\nFacebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe\ndisclosure of security bugs. In those cases, please go through the process\noutlined on that page and do not file a public issue.\n\n## Environment setup\n\n```bash\n~$ python3 -m venv venv2\n~$ source venv2/bin/activate\n(venv2) ~$ cd git/template/\n(venv2) ~/git/template $ pip3 install -r requirements-test.txt\n```\n\n## Coding Style\n\nIn your editor, install the [editorconfig](https://editorconfig.org/) extension\nwhich should ensure that you are following the same standards as us.\n\nTwo options to make sure that the code is formatted and linted properly:\n* either you run mypy and ufmt before opening up your PR.\n\n```bash\nufmt format\nflake8 --config .flake8\nmypy --ignore-missing-imports --scripts-are-modules --pretty --exclude build/ --exclude stubs/ .\n```\n\n* or you can just install [pre-commit](https://pre-commit.com/), which will make sure that all of the above is run automatically anytime you commit\nin that case, you would need to\n```bash\npip install pre-commit\n```\nthen (in the xformers repository, just once)\n```bash\npre-commit install\n```\n\nAfter these steps each of your commits will run the same linting and formatting routines as the xformers continuous integration, which greatly helps getting your PRs all green !\n\n_Read the [editorconfig](.editorconfig) file to understand the exact coding style preferences._\n\n## Testing\n\n### Static analysis\n\n```bash\nmypy --ignore-missing-imports --scripts-are-modules --pretty --exclude stubs/ .\n```\n\n### Unit tests\n\n```bash\npytest\n```\n\nor\n\n``` bash\npython -m pytest\n```\n\n### Check test coverage\n\n``` bash\npython -m pytest --cov-report term --cov=template  tests\n```\n\n### CircleCI status\n\nFrom your PR page, you can expand on the CircleCI results. For GPU test, you should see\nwhat CI has run, like:\n\n``` bash\n...\n----- generated xml file: /home/circleci/template/test-results/junit.xml ------\n================== 217 passed, 2 xfailed in 218.74s (0:03:38) ==================\nCircleCI received exit code 0\n```\n\nThe number of passed and failed should give you an idea on whether your local\ntest was the same or not.\n\n## Commit Guidelines\n\nWe follow the same guidelines as AngularJS. Each commit message consists of a **header**,\na **body** and a **footer**.  The header has a special format that includes a **type**,\nand a **subject**:\n\n```bash\n[<type>] <subject>\n<BLANK LINE>\n<body>\n<BLANK LINE>\n<footer>\n```\n\nAny line of the commit message cannot be longer 100 characters! This allows the message to be easier\nto read on github as well as in various git tools.\n\n### Type\n\nMust be one of the following:\n\n* **feat**: A new feature\n* **fix**: A bug fix\n* **cleanup**: Changes that do not affect the meaning of the code (white-space, formatting, missing\n  semi-colons, dead code removal etc.)\n* **refactor**: A code change that neither fixes a bug or adds a feature\n* **perf**: A code change that improves performance\n* **test**: Adding missing tests or fixing them\n* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation\ngeneration\n* **docs**: Documentation only changes\n\n## License\n\nBy contributing to *xFormers*, you agree that your contributions will be licensed\nunder the LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "LICENSE",
    "content": "From xFormers:\n\nCopyright (c) Facebook, Inc. and its affiliates\n\n\n===\n\nBSD 3-Clause License\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright\n   notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright\n   notice, this list of conditions and the following disclaimer in the\n   documentation and/or other materials provided with the distribution.\n\n3. Neither the names of Facebook, Deepmind Technologies, NYU, NEC Laboratories America\n   and IDIAP Research Institute nor the names of its contributors may be\n   used to endorse or promote products derived from this software without\n   specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include LICENSE\ninclude requirements.txt\ninclude version.txt\n\nrecursive-include xformers/csrc *\nrecursive-include third_party/cutlass/include *\nrecursive-include third_party/cutlass/tools/util/include *\nrecursive-include third_party/cutlass/examples *\n"
  },
  {
    "path": "README.md",
    "content": "<img src=\"./docs/assets/logo.png\" width=800>\n\n[![Open in Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/facebookresearch/xformers/blob/main/docs/source/xformers_mingpt.ipynb)\n<br/><!--\n![PyPI](https://img.shields.io/pypi/v/xformers)\n![PyPI - License](https://img.shields.io/pypi/l/xformers)\n[![Documentation Status](https://github.com/facebookresearch/xformers/actions/workflows/gh-pages.yml/badge.svg)](https://github.com/facebookresearch/xformers/actions/workflows/gh-pages.yml/badge.svg)\n-->\n[![CircleCI](https://circleci.com/gh/facebookresearch/xformers.svg?style=shield)](https://app.circleci.com/pipelines/github/facebookresearch/xformers/)\n[![Codecov](https://codecov.io/gh/facebookresearch/xformers/branch/main/graph/badge.svg?token=PKGKDR4JQM)](https://codecov.io/gh/facebookresearch/xformers)\n[![black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)\n<br/>\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)\n<!--\n[![Downloads](https://pepy.tech/badge/xformers)](https://pepy.tech/project/xformers)\n-->\n--------------------------------------------------------------------------------\n\n## xFormers - Toolbox to Accelerate Research on Transformers\n\nxFormers is:\n- **Customizable building blocks**: Independent/customizable building blocks that can be used without boilerplate code. The components are domain-agnostic and xFormers is used by researchers in vision, NLP and more.\n- **Research first**: xFormers contains bleeding-edge components, that are not yet available in mainstream libraries like PyTorch.\n- **Built with efficiency in mind**: Because speed of iteration matters, components are as fast and memory-efficient as possible. xFormers contains its own CUDA kernels, but dispatches to other libraries when relevant.\n\n## Installing xFormers\n\n* **(RECOMMENDED, linux & win) Install latest stable with pip**: Requires [PyTorch 2.10.0](https://pytorch.org/get-started/locally/)\n\n```bash\n# [linux & win] cuda 12.6 version\npip3 install -U xformers --index-url https://download.pytorch.org/whl/cu126\n# [linux & win] cuda 12.8 version\npip3 install -U xformers --index-url https://download.pytorch.org/whl/cu128\n# [linux & win] cuda 13.0 version\npip3 install -U xformers --index-url https://download.pytorch.org/whl/cu130\n# [linux only] (EXPERIMENTAL) rocm 7.1 version\npip3 install -U xformers --index-url https://download.pytorch.org/whl/rocm7.1\n```\n\n* **Development binaries**:\n\n```bash\n# Same requirements as for the stable version above\npip install --pre -U xformers\n```\n\n* **Install from source**: If you want to use with another version of PyTorch for instance (including nightly-releases)\n\n```bash\n# (Optional) Makes the build much faster\npip install ninja\n# Set TORCH_CUDA_ARCH_LIST if running and building on different GPU types\n# NOTE: pytorch must already be installed!\npip install -v --no-build-isolation -U git+https://github.com/facebookresearch/xformers.git@main#egg=xformers\n# (this can take dozens of minutes)\n```\n\n\n## Benchmarks\n\n**Memory-efficient MHA**\n![Benchmarks for ViTS](./docs/plots/mha/mha_vit.png)\n*Setup: A100 on f16, measured total time for a forward+backward pass*\n\nNote that this is exact attention, not an approximation, just by calling [`xformers.ops.memory_efficient_attention`](https://facebookresearch.github.io/xformers/components/ops.html#xformers.ops.memory_efficient_attention)\n\n**More benchmarks**\n\nxFormers provides many components, and more benchmarks are available in [BENCHMARKS.md](BENCHMARKS.md).\n\n### (Optional) Testing the installation\n\nThis command will provide information on an xFormers installation, and what kernels are built/available:\n\n```python\npython -m xformers.info\n```\n\n## Using xFormers\n\n### Key Features\n\n1. Optimized building blocks, beyond PyTorch primitives\n   1. Memory-efficient exact attention - up to 10x faster\n   2. sparse attention\n   3. block-sparse attention\n   4. fused softmax\n   5. fused linear layer\n   6. fused layer norm\n   7. fused dropout(activation(x+bias))\n   8. fused SwiGLU\n\n### Install troubleshooting\n\n\n* NVCC and the current CUDA runtime match. Depending on your setup, you may be able to change the CUDA runtime with `module unload cuda; module load cuda/xx.x`, possibly also `nvcc`\n* the version of GCC that you're using matches the current NVCC capabilities\n* the `TORCH_CUDA_ARCH_LIST` env variable is set to the architectures that you want to support. A suggested setup (slow to build but comprehensive) is `export TORCH_CUDA_ARCH_LIST=\"6.0;6.1;6.2;7.0;7.2;7.5;8.0;8.6\"`\n* If the build from source OOMs, it's possible to reduce the parallelism of ninja with `MAX_JOBS` (eg `MAX_JOBS=2`)\n* If getting error message `Filename longer than 260 characters` on Windows, make sure long paths are enabled at OS level, and also execute the command `git config --global core.longpaths true`\n\n\n### License\n\nxFormers has a BSD-style license, as found in the [LICENSE](LICENSE) file.\nIt includes code from the [triton-lang/kernels](https://github.com/triton-lang/kernels) repo.\n\n## Citing xFormers\n\nIf you use xFormers in your publication, please cite it by using the following BibTeX entry.\n\n``` bibtex\n@Misc{xFormers2022,\n  author =       {Benjamin Lefaudeux and Francisco Massa and Diana Liskovich and Wenhan Xiong and Vittorio Caggiano and Sean Naren and Min Xu and Jieru Hu and Marta Tintore and Susan Zhang and Patrick Labatut and Daniel Haziza and Luca Wehrstedt and Jeremy Reizenstein and Grigory Sizov},\n  title =        {xFormers: A modular and hackable Transformer modelling library},\n  howpublished = {\\url{https://github.com/facebookresearch/xformers}},\n  year =         {2022}\n}\n```\n\n## Credits\n\nThe following repositories are used in xFormers, either in close to original form or as an inspiration:\n\n* [Sputnik](https://github.com/google-research/sputnik)\n* [GE-SpMM](https://github.com/hgyhungry/ge-spmm)\n* [Triton](https://github.com/openai/triton)\n* [LucidRain Reformer](https://github.com/lucidrains/reformer-pytorch)\n* [RevTorch](https://github.com/RobinBruegger/RevTorch)\n* [Nystromformer](https://github.com/mlpen/Nystromformer)\n* [FairScale](https://github.com/facebookresearch/fairscale/)\n* [Pytorch Image Models](https://github.com/rwightman/pytorch-image-models)\n* [CUTLASS](https://github.com/nvidia/cutlass)\n* [Flash-Attention](https://github.com/HazyResearch/flash-attention)\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line, and also\n# from the environment for the first two.\nSPHINXOPTS    ?=\nSPHINXBUILD   ?= sphinx-build\nSOURCEDIR     = source\nBUILDDIR      = build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\nsetup:\n\tpip install -r requirements.txt\n\n.PHONY: help Makefile setup\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "recommonmark==0.5.0\ndocutils==0.17.1\nsphinx==5.0.0\ngit+https://github.com/pytorch/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme\ntorch>=1.6.0\nnumpy>=1.19.5\npyre-extensions==0.0.29\njinja2==3.1.6\neinops\n"
  },
  {
    "path": "docs/source/2d_attention_patterns.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Creating complex sparsity patterns with xformers\\n\",\n    \"\\n\",\n    \"`xformers` contains optimized GPU implementations for sparse transformers, which are specially useful when dealing with large sequences like images.\\n\",\n    \"\\n\",\n    \"In this notebook, we illustrate how one can leverage some helper functions from `xformers` to construct complex sparsity patterns via a particular structure of the `attn_mask`, which are enough to re-implement axial attention, local 2d attention and many more.\\n\",\n    \"\\n\",\n    \"Let's start with some imports\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import torch\\n\",\n    \"import xformers.components.attention.attention_patterns as AP\\n\",\n    \"\\n\",\n    \"%matplotlib inline\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's assume our sequence length is `H * W`, and let's select the middle point in the `(H * W) ** 2` for visualization purposes\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"H, W = 20, 30\\n\",\n    \"middle_point = H * W // 2 + W // 2\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Axial attention\\n\",\n    \"\\n\",\n    \"Different instantiations of the [axial attention](https://arxiv.org/abs/1912.12180) for images can be obtained via the following helper functions.\\n\",\n    \"\\n\",\n    \"They create distance matrices that can be used to generate the axial attention at a given distance\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x1008 with 6 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"hoz_dist = AP.horizontal_axial_2d_distance(H, W)\\n\",\n    \"vert_dist = AP.vertical_axial_2d_distance(H, W)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(3, 2, figsize=(15, 14))\\n\",\n    \"# full distance matrix between every two points\\n\",\n    \"axs[0, 0].imshow(hoz_dist)\\n\",\n    \"axs[0, 0].set_title(\\\"Full (H * W)^2 x (x * W)^2 distance matrix\\\")\\n\",\n    \"axs[0, 1].imshow(vert_dist)\\n\",\n    \"axs[0, 1].set_title(\\\"Full (H * W)^2 x (x * W)^2 distance matrix\\\")\\n\",\n    \"\\n\",\n    \"# select one point in the matrix and see the corresponding distance\\n\",\n    \"# for all other points\\n\",\n    \"axs[1, 0].imshow(hoz_dist[middle_point].reshape(H, W))\\n\",\n    \"axs[1, 0].set_title(\\\"Distance for one point to all others\\\")\\n\",\n    \"axs[1, 1].imshow(vert_dist[middle_point].reshape(H, W))\\n\",\n    \"axs[1, 1].set_title(\\\"Distance for one point to all others\\\")\\n\",\n    \"\\n\",\n    \"# standard instantiation of separate axial attention for a given point\\n\",\n    \"axs[2, 0].imshow(hoz_dist[middle_point].reshape(H, W) < 1)\\n\",\n    \"axs[2, 0].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"axs[2, 1].imshow(vert_dist[middle_point].reshape(H, W) < 1)\\n\",\n    \"axs[2, 1].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Axial attention', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"For the vanilla axial attention, we have the following convenience function\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"axial_pattern = AP.axial_2d_pattern(H, W)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(axial_pattern)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 attn_mask matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(axial_pattern[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Axial attention', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Local 2d distance\\n\",\n    \"\\n\",\n    \"Let's now consider a variant of a local attention for 2d grids.\\n\",\n    \"\\n\",\n    \"Let's use L2 distance for the following example, the norm can be changed via the `p` argument.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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tcC34PZ9fWVwnBWRvwV8LW4vXYpz974T13rgO4Dvx7lg3wV8lqq+WEQ+wPP8W5wIfBPwYuDbdo0V+HHcXr6/B/wzXFETWTGubxeRY1w7hB8BDoFnAf9UVQ/Pce6IiIiIiMcwRFXf0TFERERERERERERERERErEDc0xYREREREREREREREXEfI4q2iIiIiIiIiIiIiIiI+xhRtEVERERERERERERERNzHiKItIiIiIiIiIiIiIiLiPkYUbREREREREREREREREfcxomiLiIiIiIiIiIiIiIi4jxFFW0RERERERERERERExH2MKNoiIiIiIiIiIiIiIiLuY0TRFhERERERERERERERcR8jiraIiIiIiIiIiIiIiIj7GFG0RURERERERERERERE3MeIoi0iIiIiIiIiIiIiIuI+RhRtERERERERERERERER9zGiaIuIiIiIiIiIiIiIiLiPEUVbRERERERERERERETEfYwo2iIiIiIiIiIiIiIiIu5jRNEWERERERERERERERFxHyOKtoiIiIiIiIiIiIiIiPsYUbRFRERERERERERERETcx4iiLSIiIiIiIiIiIiIi4j5GFG0RERERERERERERERH3MaJoi4iIiIiIiIiIiIiIuI8RRds7GUTkiSKiIpL6n58nIn9rzfHvJSK/+vaLcDuIyP8tIt+35v2/IiL/pBnn2zGuQkR+S0Ruvj3Puwo+npeJyPUV77+7iDxNRN7rbXDu7xSRr/bfP0VEXv7WPsdjGSLymyLy4e/oOCIiIiIiIiLe+RFF2zsQIvKwiJyIyGHw78G38mn+BfANg3P+pUEcnyUiP78h1qeJyBPXvP9yEfnrwc9/1ovL4WuHIpKq6o8C7yMi7zvC9TeA/wx8KvB0EZHB+98gIr8rIne9oPmMdbHviM8Fnq+qrz/Ph72oftqa979URJ41eO13V7z2VFU9A54OfPEI103gOcCfB54jIu8yeP8vi8jPi8gtEXm9iHyHiOyfZ1yq+gJVfY9Nx/n75HvOc453FoRidh1U9b1V9Xlvh5AiIiIiIiIiHuOIou0dj49X1b3g32vfWsQi8gBuQf8/74Hjy0TkKf7HVET+PxH54JFDnw/8ueDnDwNeNvLaC1W18j8/EyeSwvP9JeDfAB/hj3834F8NznUEfDxwAfhM4FtE5EN2HNoq/B3gGbt+SEQ+WET+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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"loc_2d_dist = AP.local_2d_distance(H, W, p=2.0)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(loc_2d_dist)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 distance matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(loc_2d_dist[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Distance for one point to all others\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Local attention', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can either manually threshold the distance matrix ourselves, or use the convience function `local_2d_pattern` for generating the mask for us\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"# this is equivalent to thresholding the result of local_2d_distance\\n\",\n    \"# at distance d\\n\",\n    \"loc_2d_dist = AP.local_2d_pattern(H, W, distance=5, p=2.0)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(loc_2d_dist)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 attn_mask matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(loc_2d_dist[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Local attention', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Random sampling from a probability distribution\\n\",\n    \"\\n\",\n    \"Let's now create a random attention pattern that is sampled\\n\",\n    \"from a gaussian distribution centered at each point.\\n\",\n    \"\\n\",\n    \"For that, let's first create a Gaussian 2d distribution with variance `sigma`. \"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"gaus_2d_dist = AP.local_2d_gausian_distribution(H, W, sigma=2)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(gaus_2d_dist)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 distance matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(gaus_2d_dist[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Distance for one point to all others\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Gaussian  distribution', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's now sample from it given those values as the probability distribution, keeping the resulting matrix 95% sparse\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"sparsity = 0.95\\n\",\n    \"num_non_zeros = int((H * W) ** 2 * (1 - sparsity))\\n\",\n    \"random_gaus_2d_pattern = AP.random_pattern_from_probability_matrix(gaus_2d_dist, num_non_zeros)\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(random_gaus_2d_pattern)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 attn_mask matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(random_gaus_2d_pattern[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Random sampling from a Gaussian  distribution', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Combining arbitrary patterns\\n\",\n    \"\\n\",\n    \"The power of this approach of considering those different instantiations of efficient transformers as just a sparse pattern is that it becomes trivial to combine multiple patterns together.\\n\",\n    \"\\n\",\n    \"For example, using a combination of axial attention + local attention + random sampling can be done as simply as follows:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 1080x504 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"att_mask = axial_pattern | loc_2d_dist | random_gaus_2d_pattern\\n\",\n    \"\\n\",\n    \"fig, axs = plt.subplots(1, 2, figsize=(15, 7))\\n\",\n    \"# full sparse matrix mask between every two points\\n\",\n    \"# to be used in attn_mask\\n\",\n    \"axs[0].imshow(att_mask)\\n\",\n    \"axs[0].set_title(\\\"Full (H * W)^2 x (x * W)^2 attn_mask matrix\\\")\\n\",\n    \"# and a viaualization for a given point\\n\",\n    \"axs[1].imshow(att_mask[middle_point].reshape(H, W))\\n\",\n    \"axs[1].set_title(\\\"Attention mask for one select point\\\")\\n\",\n    \"\\n\",\n    \"fig.suptitle('Combining different patterns together', fontsize=16)\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's check the amount of sparsity that this pattern contains. The optimized implementations in `xformers` have competitive performance for sparsity patterns as low as 70%.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Sparsity level: 0.8392416666666667\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(f\\\"Sparsity level: {1 - float(att_mask.count_nonzero()) / att_mask.numel()}\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.8\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "docs/source/_static/css/customize.css",
    "content": "/* Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. */\n/*\n * some extra css to make markdown look similar between github/sphinx\n */\n\n\n /* this is not actually being used at the moment */\n .tutorials-header .header-logo  {\n    background-image: url(\"../images/xformers-logo-dark.svg\");\n    background-repeat: no-repeat;\n    background-position: center;\n}\n\n/* .header-logo {\n    background-image: url(\"../images/fairscale-logo.svg\");\n} */\n\n/* .footer-logo {\n    background-image: url(\"../images/fairscale-logo-icon.svg\");\n} */\n"
  },
  {
    "path": "docs/source/_templates/layout.html",
    "content": "{# TEMPLATE VAR SETTINGS #}\n{%- set url_root = pathto('', 1) %}\n{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}\n{%- if not embedded and docstitle %}\n{%- set titlesuffix = \" | \"|safe + docstitle|e %}\n{%- else %}\n{%- set titlesuffix = \"\" %}\n{%- endif %}\n{%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %}\n{% import 'theme_variables.jinja' as theme_variables %}\n\n<!DOCTYPE html>\n<!--[if IE 8]><html class=\"no-js lt-ie9\" lang=\"{{ lang_attr }}\" > <![endif]-->\n<!--[if gt IE 8]><!-->\n<html class=\"no-js\" lang=\"{{ lang_attr }}\">\n<!--<![endif]-->\n\n<head>\n  <meta charset=\"utf-8\">\n  {{ metatags }}\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  {% block htmltitle %}\n  <title>{{ title|striptags|e }}{{ titlesuffix }}</title>\n  {% endblock %}\n  <script src=\"{{ pathto('_static/js/ga.js', 1) }}\"></script>\n  <script src=\"{{ pathto('_static/js/redirect.js', 1) }}\"></script>\n  {# FAVICON #}\n  <link rel=\"shortcut icon\" href=\"{{ pathto('_static/images/favicon.png', 1) }}\" />\n  {# CANONICAL URL #}\n  {% if theme_canonical_url %}\n  <link rel=\"canonical\" href=\"{{ theme_canonical_url }}{{ pagename }}.html\" />\n  {% endif %}\n  <meta property=\"og:title\" content=\"{{ title|striptags|e }}{{ titlesuffix }}\">\n  <meta name=\"description\" content=\"{{ theme_variables.og['description'] }}\">\n  <meta property=\"og:description\" content=\"{{ theme_variables.og['description'] }}\">\n  <!--<meta property=\"og:image\" content=\"https://mmf.sh/img/logo.png\">-->\n  <!--<meta property=\"twitter:image\" content=\"https://mmf.sh/img/logo.png\">-->\n  <meta name=\"twitter:image:alt\" content=\"Image for xFormers\">\n  <meta name=\"twitter:card\" content=\"summary_large_image\">\n  {# CSS #}\n\n  {# OPENSEARCH #}\n  {% if not embedded %}\n  {% if use_opensearch %}\n  <link rel=\"search\" type=\"application/opensearchdescription+xml\"\n    title=\"{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}\"\n    href=\"{{ pathto('_static/opensearch.xml', 1) }}\" />\n  {% endif %}\n\n  {% endif %}\n\n  <link rel=\"stylesheet\" href=\"{{ pathto('_static/' + style, 1) }}\" type=\"text/css\" />\n  <!-- <link rel=\"stylesheet\" href=\"{{ pathto('_static/pygments.css', 1) }}\" type=\"text/css\" /> -->\n  {%- for css in css_files %}\n  {%- if css|attr(\"rel\") %}\n  <link rel=\"{{ css.rel }}\" href=\"{{ pathto(css.filename, 1) }}\" type=\"text/css\" {% if css.title is not none %}\n    title=\"{{ css.title }}\" {% endif %} />\n  {%- else %}\n  <link rel=\"stylesheet\" href=\"{{ pathto(css, 1) }}\" type=\"text/css\" />\n  {%- endif %}\n  {%- endfor %}\n  {%- for cssfile in extra_css_files %}\n  <link rel=\"stylesheet\" href=\"{{ pathto(cssfile, 1) }}\" type=\"text/css\" />\n  {%- endfor %}\n\n  {%- block linktags %}\n  {%- if hasdoc('about') %}\n  <link rel=\"author\" title=\"{{ _('About these documents') }}\" href=\"{{ pathto('about') }}\" />\n  {%- endif %}\n  {%- if hasdoc('genindex') %}\n  <link rel=\"index\" title=\"{{ _('Index') }}\" href=\"{{ pathto('genindex') }}\" />\n  {%- endif %}\n  {%- if hasdoc('search') %}\n  <link rel=\"search\" title=\"{{ _('Search') }}\" href=\"{{ pathto('search') }}\" />\n  {%- endif %}\n  {%- if hasdoc('copyright') %}\n  <link rel=\"copyright\" title=\"{{ _('Copyright') }}\" href=\"{{ pathto('copyright') }}\" />\n  {%- endif %}\n  {%- if next %}\n  <link rel=\"next\" title=\"{{ next.title|striptags|e }}\" href=\"{{ next.link|e }}\" />\n  {%- endif %}\n  {%- if prev %}\n  <link rel=\"prev\" title=\"{{ prev.title|striptags|e }}\" href=\"{{ prev.link|e }}\" />\n  {%- endif %}\n  {%- endblock %}\n  {%- block extrahead %} {% endblock %}\n\n  {# Keep modernizr in head - http://modernizr.com/docs/#installing #}\n  <script src=\"{{ pathto('_static/js/modernizr.min.js', 1) }}\"></script>\n\n  {% include \"fonts.html\" %}\n</head>\n\n<div class=\"container-fluid header-holder tutorials-header\" id=\"header-holder\">\n  <div class=\"container\">\n    <div class=\"header-container\">\n      <a class=\"header-logo\" href=\"{{ theme_variables.external_urls['home'] }}\"><img\n          src=\"{{ pathto('_static/logo.png', 1) }}\" style=\"padding-right: 90px;\" width=\"280px\" height=\"53px\"></a>\n\n      <div class=\"main-menu\">\n        <ul>\n          <li>\n            <a href=\"{{ theme_variables.external_urls['github'] }}\"> xFormers Github</a>\n          </li>\n        </ul>\n      </div>\n\n      <a class=\"main-menu-open-button\" href=\"#\" data-behavior=\"open-mobile-menu\"></a>\n    </div>\n\n  </div>\n</div>\n\n\n<body class=\"pytorch-body\">\n\n  {% block extrabody %} {% endblock %}\n\n  {# SIDE NAV, TOGGLES ON MOBILE #}\n\n  <div class=\"table-of-contents-link-wrapper\">\n    <span>Table of Contents</span>\n    <a href=\"#\" class=\"toggle-table-of-contents\" data-behavior=\"toggle-table-of-contents\"></a>\n  </div>\n\n  <nav data-toggle=\"wy-nav-shift\" class=\"pytorch-left-menu\" id=\"pytorch-left-menu\">\n    <div class=\"pytorch-side-scroll\">\n      <div class=\"pytorch-menu pytorch-menu-vertical\" data-spy=\"affix\" role=\"navigation\" aria-label=\"main navigation\">\n        <div class=\"pytorch-left-menu-search\">\n          {% block sidebartitle %}\n\n          {% if theme_display_version %}\n          {%- set nav_version = version %}\n          {% if READTHEDOCS and current_version %}\n          {%- set nav_version = current_version %}\n          {% endif %}\n          {% if nav_version %}\n          <div class=\"version\">\n            {{ nav_version }}\n          </div>\n          {% endif %}\n          {% endif %}\n\n          {% include \"searchbox.html\" %}\n\n          {% endblock %}\n        </div>\n\n        {% block menu %}\n        {#\n        The singlehtml builder doesn't handle this toctree call when the\n        toctree is empty. Skip building this for now.\n        #}\n        {% if 'singlehtml' not in builder %}\n        {% set global_toc = toctree(maxdepth=1,\n        collapse=theme_collapse_navigation|tobool,\n        includehidden=theme_includehidden|tobool,\n        titles_only=theme_titles_only|tobool) %}\n        {% endif %}\n        {% if global_toc %}\n        {{ global_toc }}\n        {% else %}\n        <!-- Local TOC -->\n        <div class=\"local-toc\">{{ toc }}</div>\n        {% endif %}\n        {% endblock %}\n      </div>\n    </div>\n  </nav>\n\n  <div class=\"pytorch-container\">\n    <div class=\"pytorch-page-level-bar\" id=\"pytorch-page-level-bar\">\n      <div class=\"pytorch-breadcrumbs-wrapper\">\n        {% include \"breadcrumbs.html\" %}\n      </div>\n\n      <div class=\"pytorch-shortcuts-wrapper\" id=\"pytorch-shortcuts-wrapper\">\n        Shortcuts\n      </div>\n    </div>\n\n    <section data-toggle=\"wy-nav-shift\" id=\"pytorch-content-wrap\" class=\"pytorch-content-wrap\">\n      <div class=\"pytorch-content-left\">\n\n        {% if theme_pytorch_project == 'tutorials' %}\n\n        <div class=\"pytorch-call-to-action-links\">\n          <div id=\"tutorial-type\">{{ pagename }}</div>\n\n          <div id=\"google-colab-link\">\n            <img class=\"call-to-action-img\" src=\"{{ pathto('_static/images/pytorch-colab.svg', 1) }}\" />\n            <div class=\"call-to-action-desktop-view\">Run in Google Colab</div>\n            <div class=\"call-to-action-mobile-view\">Colab</div>\n          </div>\n          <div id=\"download-notebook-link\">\n            <img class=\"call-to-action-notebook-img\" src=\"{{ pathto('_static/images/pytorch-download.svg', 1) }}\" />\n            <div class=\"call-to-action-desktop-view\">Download Notebook</div>\n            <div class=\"call-to-action-mobile-view\">Notebook</div>\n          </div>\n          <div id=\"github-view-link\">\n            <img class=\"call-to-action-img\" src=\"{{ pathto('_static/images/pytorch-github.svg', 1) }}\" />\n            <div class=\"call-to-action-desktop-view\">View on GitHub</div>\n            <div class=\"call-to-action-mobile-view\">GitHub</div>\n          </div>\n        </div>\n\n        {% endif %}\n\n        {%- block content %}\n        {% if theme_style_external_links|tobool %}\n        <div class=\"rst-content style-external-links\">\n          {% else %}\n          <div class=\"rst-content\">\n            {% endif %}\n            <div role=\"main\" class=\"main-content\" itemscope=\"itemscope\" itemtype=\"http://schema.org/Article\">\n              {%- block document %}\n              <article itemprop=\"articleBody\" id=\"pytorch-article\" class=\"pytorch-article\">\n                {% block body %}{% endblock %}\n              </article>\n              {% if self.comments()|trim %}\n              <div class=\"articleComments\">\n                {% block comments %}{% endblock %}\n              </div>\n              {% endif%}\n            </div>\n            {%- endblock %}\n            {% include \"footer.html\" %}\n          </div>\n          {%- endblock %}\n        </div>\n\n        <!-- <div class=\"pytorch-content-right\" id=\"pytorch-content-right\">\n          <div class=\"pytorch-right-menu\" id=\"pytorch-right-menu\">\n            <div class=\"pytorch-side-scroll\" id=\"pytorch-side-scroll-right\">\n              {{ toc }}\n            </div>\n          </div>\n        </div> -->\n    </section>\n  </div>\n\n  {% include \"versions.html\" %}\n\n  {% if not embedded %}\n\n  {% if sphinx_version >= \"1.8.0\" %}\n  <script type=\"text/javascript\" id=\"documentation_options\" data-url_root=\"{{ pathto('', 1) }}\"\n    src=\"{{ pathto('_static/documentation_options.js', 1) }}\"></script>\n  {%- for scriptfile in script_files %}\n  {{ js_tag(scriptfile) }}\n  {%- endfor %}\n  {% else %}\n  <script type=\"text/javascript\">\n    var DOCUMENTATION_OPTIONS = {\n      URL_ROOT: '{{ url_root }}',\n      VERSION: '{{ release|e }}',\n      LANGUAGE: '{{ language }}',\n      COLLAPSE_INDEX: false,\n      FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',\n      HAS_SOURCE: {{ has_source| lower }},\n    SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'\n           };\n  </script>\n  {%- for scriptfile in script_files %}\n  <script type=\"text/javascript\" src=\"{{ pathto(scriptfile, 1) }}\"></script>\n  {%- endfor %}\n  {% endif %}\n\n  {% endif %}\n\n  <script type=\"text/javascript\" src=\"{{ pathto('_static/js/vendor/popper.min.js', 1) }}\"></script>\n  <script type=\"text/javascript\" src=\"{{ pathto('_static/js/vendor/bootstrap.min.js', 1) }}\"></script>\n  <script src=\"https://cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js\"></script>\n  <script type=\"text/javascript\" src=\"{{ pathto('_static/js/theme.js', 1) }}\"></script>\n\n  <script type=\"text/javascript\">\n    jQuery(function () {\n      SphinxRtdTheme.Navigation.enable({{ 'true' if theme_sticky_navigation | tobool else 'false' }});\n      });\n  </script>\n\n  {%- block footer %} {% endblock %}\n\n  <!-- Begin Footer -->\n\n\n  <footer class=\"site-footer\">\n    <div class=\"container footer-container\">\n      <div class=\"footer-logo-wrapper\">\n        <a href=\"{{ theme_variables.external_urls['home'] }}\" class=\"footer-logo\"></a>\n      </div>\n      <div class=\"footer-links-wrapper\">\n        <div class=\"footer-links-col\">\n          <ul>\n            <li class=\"list-title\"><a href=\"{{ theme_variables.external_urls['home'] }}\">fairscale</a></li>\n            <li><a href=\"{{ theme_variables.external_urls['get_started'] }}\">Get Started</a></li>\n            <li><a href=\"{{ theme_variables.external_urls['contributing'] }}\">Contributing</a></li>\n          </ul>\n        </div>\n\n        <div class=\"footer-links-col\">\n          <ul>\n            <li class=\"list-title\"><a href=\"{{ theme_variables.external_urls['resources'] }}\">Resources</a></li>\n            <li><a href=\"{{ theme_variables.external_urls['docs'] }}\">Docs</a></li>\n            <li><a href=\"{{ theme_variables.external_urls['github_issues'] }}\" target=\"_blank\">Github Issues</a></li>\n            <li><a href=\"https://opensource.facebook.com/legal/terms\">Terms of Use</a></li>\n            <li><a href=\"https://opensource.facebook.com/legal/privacy\">Privacy Policy</a></li>\n          </ul>\n        </div>\n      </div>\n    </div>\n    </div>\n  </footer>\n\n  {% include \"cookie_banner.html\" %}\n\n  <!-- End Footer -->\n\n  <!-- Begin Mobile Menu -->\n\n  <div class=\"mobile-main-menu\">\n    <div class=\"container-fluid\">\n      <div class=\"container\">\n        <div class=\"mobile-main-menu-header-container\">\n          <a class=\"header-logo\" href=\"{{ theme_variables.external_urls['home'] }}\" aria-label=\"fairscale\"></a>\n          <a class=\"main-menu-close-button\" href=\"#\" data-behavior=\"close-mobile-menu\"></a>\n        </div>\n      </div>\n    </div>\n\n    <div class=\"mobile-main-menu-links-container\">\n      <div class=\"main-menu\">\n        <ul>\n          <li>\n            <a href=\"{{ theme_variables.external_urls['get_started'] }}\">Get Started</a>\n          </li>\n\n          <li {%- if theme_pytorch_project=='tutorials' %} class=\"active\" {%- endif %}>\n            <a href=\"{{ theme_variables.external_urls['docs'] }}\">Docs</a>\n          </li>\n\n          <li>\n            <a href=\"{{ theme_variables.external_urls['github'] }}\">Github</a>\n          </li>\n        </ul>\n      </div>\n    </div>\n  </div>\n\n  <!-- End Mobile Menu -->\n\n  <script type=\"text/javascript\" src=\"{{ pathto('_static/js/vendor/anchor.min.js', 1) }}\"></script>\n\n  <script type=\"text/javascript\">\n    $(document).ready(function () {\n      mobileMenu.bind();\n      mobileTOC.bind();\n      pytorchAnchors.bind();\n      sideMenus.bind();\n      scrollToAnchor.bind();\n      highlightNavigation.bind();\n      mainMenuDropdown.bind();\n      filterTags.bind();\n\n      // Add class to links that have code blocks, since we cannot create links in code blocks\n      $(\"article.pytorch-article a span.pre\").each(function (e) {\n        $(this).closest(\"a\").addClass(\"has-code\");\n      });\n    })\n  </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "docs/source/_templates/theme_variables.jinja",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\n{%-\nset external_urls = {\n  'github': 'https://github.com/facebookresearch/xformers',\n  'github_issues': 'https://github.com/facebookresearch/xformers/issues',\n  'contributing': 'https://github.com/facebookresearch/xformers/blob/master/CONTRIBUTING.md',\n  'docs': 'https://github.com/facebookresearch/xformers',\n  'home': 'https://github.com/facebookresearch/xformers',\n  'get_started': 'https://github.com/facebookresearch/xformers/blob/master/README.md',\n  'brand_guidelines': 'https://pytorch.org/assets/brand-guidelines/PyTorch-Brand-Guidelines.pdf'\n}\n-%}\n{%-\nset og = {\n  'description': 'API docs for xFormers. xFormers is a PyTorch extension library for composable and optimized Transformer blocks.'\n}\n-%}\n"
  },
  {
    "path": "docs/source/components/index.rst",
    "content": "API Reference\n=============\n\n.. toctree::\n   :maxdepth: 2\n\n   ops\n"
  },
  {
    "path": "docs/source/components/ops.rst",
    "content": "xFormers optimized operators\n============================================================\n\nMemory-efficient attention\n---------------------------\n\n.. automodule:: xformers.ops\n    :members: memory_efficient_attention, AttentionOpBase\n    :show-inheritance:\n    :imported-members:\n\n\nAvailable implementations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n.. automodule:: xformers.ops.fmha.cutlass\n    :members: FwOp, BwOp\n    :member-order: bysource\n\n.. automodule:: xformers.ops.fmha.flash\n    :members: FwOp, BwOp\n    :member-order: bysource\n\n.. automodule:: xformers.ops.fmha.small_k\n    :members: FwOp, BwOp\n    :member-order: bysource\n\n.. automodule:: xformers.ops.fmha.ck\n    :members: FwOp, BwOp\n    :member-order: bysource\n\n.. automodule:: xformers.ops.fmha.ck_decoder\n    :members: FwOp\n    :member-order: bysource\n\n.. automodule:: xformers.ops.fmha.ck_splitk\n    :members: FwOp\n    :member-order: bysource\n\nAttention biases\n~~~~~~~~~~~~~~~~~~~~\n\n.. automodule:: xformers.ops.fmha.attn_bias\n    :members:\n    :show-inheritance:\n    :member-order: bysource\n\nPartial Attention\n~~~~~~~~~~~~~~~~~~~~\n\n.. automodule:: xformers.ops.fmha\n    :members: memory_efficient_attention_partial, merge_attentions\n    :member-order: bysource\n\n\nNon-autograd implementations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n.. automodule:: xformers.ops.fmha\n    :members: memory_efficient_attention_forward, memory_efficient_attention_forward_requires_grad, memory_efficient_attention_backward\n    :show-inheritance:\n    :imported-members:\n    :member-order: bysource\n"
  },
  {
    "path": "docs/source/conf.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\n# type: ignore\n# Configuration file for the Sphinx documentation builder.\n#\n# This file only contains a selection of the most common options. For a full\n# list see the documentation:\n# https://www.sphinx-doc.org/en/master/usage/configuration.html\n\n# -- Path setup --------------------------------------------------------------\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport sys\nfrom pathlib import Path\nfrom typing import Any, List\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nfrom recommonmark.transform import AutoStructify\n\nsys.path.insert(0, os.path.abspath(\"../..\"))\n\n# -- Project information -----------------------------------------------------\n\nproject = \"xFormers\"\ncopyright = \"Copyright © 2021 Meta Platforms, Inc\"\nauthor = \"Facebook AI Research\"\n\nroot_dir = Path(__file__).resolve().parent.parent.parent\n# The full version, including alpha/beta/rc tags\nrelease = (root_dir / \"version.txt\").read_text().strip()\n\n# -- General configuration ---------------------------------------------------\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.autosectionlabel\",\n    \"sphinx.ext.napoleon\",  # support NumPy and Google style docstrings\n    \"recommonmark\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.todo\",\n    \"sphinx.ext.coverage\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.viewcode\",\n    \"sphinx.ext.githubpages\",\n    \"sphinx.ext.doctest\",\n    \"sphinx.ext.ifconfig\",\n]\n\n# autosectionlabel throws warnings if section names are duplicated.\n# The following tells autosectionlabel to not throw a warning for\n# duplicated section names that are in different documents.\nautosectionlabel_prefix_document = True\n\n# -- Configurations for plugins ------------\nnapoleon_google_docstring = True\nnapoleon_include_init_with_doc = True\nnapoleon_include_special_with_doc = True\nnapoleon_numpy_docstring = False\nnapoleon_use_rtype = False\nautodoc_inherit_docstrings = False\nautodoc_member_order = \"bysource\"\n\nintersphinx_mapping = {\n    \"python\": (\"https://docs.python.org/3.6\", None),\n    \"numpy\": (\"https://numpy.org/doc/stable/\", None),\n    \"torch\": (\"https://pytorch.org/docs/master\", None),\n}\n# -------------------------\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This pattern also affects html_static_path and html_extra_path.\nexclude_patterns: List[Any] = []\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\nsource_suffix = [\".rst\", \".md\"]\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = True\n\n# -- Options for HTML output -------------------------------------------------\n\n\nhtml_theme = \"pytorch_sphinx_theme\"\ntemplates_path = [\"_templates\"]\n\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\nhtml_theme_options = {\n    \"includehidden\": True,\n    \"canonical_url\": \"https://facebookresearch.github.io/xformers\",\n    \"pytorch_project\": \"docs\",\n    \"logo_only\": True,  # default = False\n}\n\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# setting custom stylesheets https://stackoverflow.com/a/34420612\nhtml_context = {\"css_files\": [\"_static/css/customize.css\"]}\n\n# -- Options for HTMLHelp output ------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"xformersdocs\"\ngithub_doc_root = \"https://github.com/facebookresearch/xformers/tree/main/docs/\"\n\n\n# Over-ride PyTorch Sphinx css\ndef setup(app):\n    app.add_config_value(\n        \"recommonmark_config\",\n        {\n            \"url_resolver\": lambda url: github_doc_root + url,\n            \"auto_toc_tree_section\": \"Contents\",\n            \"enable_math\": True,\n            \"enable_inline_math\": True,\n            \"enable_eval_rst\": True,\n            \"enable_auto_toc_tree\": True,\n        },\n        True,\n    )\n    app.add_transform(AutoStructify)\n    app.add_css_file(\"css/customize.css\")\n"
  },
  {
    "path": "docs/source/index.rst",
    "content": "\nWelcome to xFormers's documentation!\n=====================================\n\n*xFormers* is a PyTorch based library which hosts flexible Transformers parts.\nThey are interoperable and optimized building blocks, which can optionally be combined\nto create some state of the art models.\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Index\n   :hidden:\n\n   what_is_xformers\n\n|\n|\n\n.. toctree::\n   :maxdepth: 2\n   :caption: Components Documentation\n\n   components/index\n\n|\n|\n"
  },
  {
    "path": "docs/source/swin_transformer.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e69e9896-4be5-4706-9b49-cb772d02e8d4\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Swin Transformers as a special sparsity pattern\\n\",\n    \"\\n\",\n    \"In this notebook, we will show how the recently-introduced [Swin Transformers](https://arxiv.org/abs/2103.14030) can be cast\\n\",\n    \"as a sparse transformer with a particular sparsity pattern.\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"Swin Transformers is a hierarchical Transformer whose representation is computed with shifted windows.\\n\",\n    \"The shifted windowing scheme brings efficiency by limiting self-attention computation to non-overlapping local windows while also allowing for cross-window connection\\n\",\n    \"\\n\",\n    \"<img src=\\\"https://github.com/microsoft/Swin-Transformer/raw/main/figures/teaser.png\\\" alt=\\\"drawing\\\" width=\\\"50%\\\"/>\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"In this notebook, we will cover:\\n\",\n    \"- what type of self-attention is needed to replicate a Swin Transformer\\n\",\n    \"- we will show how one can modify their pre-trained Swin Transformer to use the sparse kernels from xformers instead of hand writing the Swin Transformer self-attention by hand.\\n\",\n    \"\\n\",\n    \"Let's start with a few imports. In this notebook, the vanilla Swin Transformer will be taken from `timm`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"1beec17c-cdec-4c54-afca-61423c1aab58\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import matplotlib.pyplot as plt\\n\",\n    \"import copy\\n\",\n    \"import torch\\n\",\n    \"from torch import nn\\n\",\n    \"from torch.utils import benchmark\\n\",\n    \"\\n\",\n    \"import xformers.components.attention.attention_patterns as AP\\n\",\n    \"from xformers.components.attention.core import scaled_dot_product_attention\\n\",\n    \"from xformers.components.attention._sputnik_sparse import SparseCS\\n\",\n    \"\\n\",\n    \"import timm\\n\",\n    \"\\n\",\n    \"%matplotlib inline\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"9072a48e-ba89-4093-ae7e-22706602f11e\",\n   \"metadata\": {},\n   \"source\": [\n    \"## What sparsity pattern does Swin Transformer correspond to?\\n\",\n    \"\\n\",\n    \"In xformers, we provide for reference a default implementation of the attention pattern that corresponds to the Swin Transformer architecture.\\n\",\n    \"\\n\",\n    \"It can be found together with the other attention patterns in `xformers.components.attention.attention_patterns`.\\n\",\n    \"\\n\",\n    \"Let's try it out on the example case from above, on an image of size 8x8, and windows of size 4:\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"id\": \"57323b78-3b3b-457a-95d8-1f55fdcdddd6\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 504x1008 with 2 Axes>\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"H, W = 8, 8\\n\",\n    \"window_size = 4\\n\",\n    \"\\n\",\n    \"mask = AP.swin_attention_pattern(H, W, window_size, shift_size=0)\\n\",\n    \"mask_shifted = AP.swin_attention_pattern(H, W, window_size, shift_size=2)\\n\",\n    \"\\n\",\n    \"fig = plt.figure(figsize=(7, 14))\\n\",\n    \"ax = fig.add_subplot(1, 2, 1)\\n\",\n    \"ax.imshow(mask)\\n\",\n    \"plt.xticks([])\\n\",\n    \"plt.yticks([])\\n\",\n    \"ax = fig.add_subplot(1, 2, 2)\\n\",\n    \"ax.imshow(mask_shifted)\\n\",\n    \"plt.xticks([])\\n\",\n    \"plt.yticks([])\\n\",\n    \"fig.tight_layout()\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"26ca8db6-9897-4239-89fb-f48b1f0d75b8\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now let's visualize the self-attention for every pixel in the image. Every sub-image corresponds to the self-attention for one pixel\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"id\": \"10fe9b1e-a443-4c54-a141-d3d08969efe6\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 864x864 with 64 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"fig = plt.figure(figsize=(12, 12))\\n\",\n    \"for i in range(H * W):\\n\",\n    \"    ax = fig.add_subplot(H, W, i + 1)\\n\",\n    \"    ax.imshow(mask[i].reshape(H, W))\\n\",\n    \"    ax.grid(color='k', linestyle='-', linewidth=1)\\n\",\n    \"    ax.set_xticks(torch.arange(0.5, W))\\n\",\n    \"    ax.set_yticks(torch.arange(0.5, H))\\n\",\n    \"    ax.set_xticklabels([])\\n\",\n    \"    ax.set_yticklabels([])\\n\",\n    \"fig.tight_layout()\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"747678ef-18d0-4e13-969b-da389f7e3479\",\n   \"metadata\": {},\n   \"source\": [\n    \"And for the shifted case\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"id\": \"01274104-c52a-4740-85bf-67363300322b\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": \"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\\n\",\n      \"text/plain\": [\n       \"<Figure size 864x864 with 64 Axes>\"\n      ]\n     },\n     \"metadata\": {\n      \"needs_background\": \"light\"\n     },\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"fig = plt.figure(figsize=(12, 12))\\n\",\n    \"for i in range(H * W):\\n\",\n    \"    ax = fig.add_subplot(H, W, i + 1)\\n\",\n    \"    ax.imshow(mask_shifted[i].reshape(H, W))\\n\",\n    \"    ax.grid(color='k', linestyle='-', linewidth=1)\\n\",\n    \"    ax.set_xticks(torch.arange(0.5, W))\\n\",\n    \"    ax.set_yticks(torch.arange(0.5, H))\\n\",\n    \"    ax.set_xticklabels([])\\n\",\n    \"    ax.set_yticklabels([])\\n\",\n    \"fig.tight_layout()\\n\",\n    \"plt.show()\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"dd27ef92-1ba7-405a-9545-251f94f29461\",\n   \"metadata\": {},\n   \"source\": [\n    \"We can see that the self-attention maps correspond to the images in the paper (shown in the top of the notebook), illustrating that indeed a custom sparsity pattern is enough to reproduce Swin Transformer without having to ressort to implementing custom code.\\n\",\n    \"\\n\",\n    \"Plus, it is trivial to extend Swin Transformer with other attention patterns (such as local 2d, axial and more, see [the 2d attetnion patterns notebook](https://github.com/fairinternal/xformers/blob/main/docs/source/2d_attention_patterns.ipynb) for more examples.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"699fd7bc-377b-4786-8dc7-732619ddb89e\",\n   \"metadata\": {},\n   \"source\": [\n    \"## Using Swin Transformers as a sparse Transformer in your model\\n\",\n    \"\\n\",\n    \"Now that we know that we can represent a Swin Transformer as a particular instantiation of a sparse Transformer, let's use xformers efficient sparse kernels to see\\n\",\n    \"what type of speed / memory trade-offs we get by casting a Swin Transformer as a sparse Transformer.\\n\",\n    \"\\n\",\n    \"To facilitate benchmarking and memory profiling, let's define a function that takes a generic callable and executes it, measuring the execution time and the GPU memory\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"id\": \"b072ae40-9bf3-4ab8-9717-acf2e8ffe981\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def profile_model(fn, min_run_time=2):\\n\",\n    \"    torch.cuda.reset_peak_memory_stats()\\n\",\n    \"    torch.cuda.synchronize()\\n\",\n    \"    res = benchmark.Timer(\\n\",\n    \"        stmt='fn()',\\n\",\n    \"        globals={\\\"fn\\\": fn},\\n\",\n    \"        label=\\\"profile\\\",\\n\",\n    \"        sub_label=\\\"\\\",\\n\",\n    \"        description=\\\"\\\"\\n\",\n    \"    ).blocked_autorange(min_run_time=min_run_time)\\n\",\n    \"    torch.cuda.synchronize()\\n\",\n    \"    memory = torch.cuda.max_memory_allocated() / 2 ** 20\\n\",\n    \"    memory = f\\\"Memory used: {memory} MB\\\"\\n\",\n    \"    print(res)\\n\",\n    \"    print(memory)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"1edb40c1-98ce-4ae1-a486-42022f3b6b1b\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now it comes the core of it. We will implement an `Attention` module following the same API and modules as timm's, but using our `scaled_dot_product_attention` function.\\n\",\n    \"\\n\",\n    \"Note the similarities between this implementation and the one from the [vision transformers notebook](https://github.com/fairinternal/xformers/blob/main/docs/source/vision_transformers.ipynb).\\n\",\n    \"\\n\",\n    \"Note that we are not implementing relative positional embedding for simplicity\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"id\": \"8def591e-be74-489a-af6b-45f90e13aadc\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from timm.models.layers import Mlp, DropPath\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"# exact the same one as from https://github.com/fairinternal/xformers/blob/main/docs/source/vision_transformers.ipynb\\n\",\n    \"class Attention(torch.nn.Module):\\n\",\n    \"    def __init__(\\n\",\n    \"        self,\\n\",\n    \"        dim,\\n\",\n    \"        num_heads=8,\\n\",\n    \"        qkv_bias=False,\\n\",\n    \"        attn_drop=0.0,\\n\",\n    \"        proj_drop=0.0,\\n\",\n    \"        attn_mask=None,\\n\",\n    \"        **kwargs\\n\",\n    \"    ):\\n\",\n    \"        super().__init__()\\n\",\n    \"        self.num_heads = num_heads\\n\",\n    \"\\n\",\n    \"        self.qkv = torch.nn.Linear(dim, dim * 3, bias=qkv_bias)\\n\",\n    \"        self.attn_drop = torch.nn.Dropout(attn_drop)\\n\",\n    \"        self.proj = torch.nn.Linear(dim, dim)\\n\",\n    \"        self.proj_drop = torch.nn.Dropout(proj_drop)\\n\",\n    \"        self.attn_mask = attn_mask\\n\",\n    \"\\n\",\n    \"    def forward(self, x):\\n\",\n    \"        B, N, C = x.shape\\n\",\n    \"        qkv = (\\n\",\n    \"            self.qkv(x)\\n\",\n    \"            .reshape(B, N, 3, self.num_heads, C // self.num_heads)\\n\",\n    \"            .permute(2, 0, 3, 1, 4)\\n\",\n    \"        )\\n\",\n    \"\\n\",\n    \"        qkv = qkv.flatten(1, 2)\\n\",\n    \"\\n\",\n    \"        q, k, v = qkv.unbind()\\n\",\n    \"        \\n\",\n    \"        x = scaled_dot_product_attention(q, k, v, self.attn_mask, dropout=self.attn_drop)\\n\",\n    \"        \\n\",\n    \"        x = x.reshape(B, self.num_heads, N, C // self.num_heads)\\n\",\n    \"\\n\",\n    \"        x = x.transpose(1, 2).reshape(B, N, C)\\n\",\n    \"        x = self.proj(x)\\n\",\n    \"        x = self.proj_drop(x)\\n\",\n    \"        return x\\n\",\n    \"    \\n\",\n    \"\\n\",\n    \"# almost copy and paste from timm's implementation, but removing the unneeded elements\\n\",\n    \"# as we don't need to perform the image partitioning anymore\\n\",\n    \"# Note that we call our swin_attention_pattern in the constructor\\n\",\n    \"# to generate the custom sparsity pattern\\n\",\n    \"class SwinTransformerBlock(nn.Module):\\n\",\n    \"    r\\\"\\\"\\\" Swin Transformer Block.\\n\",\n    \"    Args:\\n\",\n    \"        dim (int): Number of input channels.\\n\",\n    \"        input_resolution (tuple[int]): Input resulotion.\\n\",\n    \"        num_heads (int): Number of attention heads.\\n\",\n    \"        window_size (int): Window size.\\n\",\n    \"        shift_size (int): Shift size for SW-MSA.\\n\",\n    \"        mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.\\n\",\n    \"        qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True\\n\",\n    \"        qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.\\n\",\n    \"        drop (float, optional): Dropout rate. Default: 0.0\\n\",\n    \"        attn_drop (float, optional): Attention dropout rate. Default: 0.0\\n\",\n    \"        drop_path (float, optional): Stochastic depth rate. Default: 0.0\\n\",\n    \"        act_layer (nn.Module, optional): Activation layer. Default: nn.GELU\\n\",\n    \"        norm_layer (nn.Module, optional): Normalization layer.  Default: nn.LayerNorm\\n\",\n    \"    \\\"\\\"\\\"\\n\",\n    \"\\n\",\n    \"    def __init__(self, dim, input_resolution, num_heads, window_size=7, shift_size=0,\\n\",\n    \"                 mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., drop_path=0.,\\n\",\n    \"                 act_layer=nn.GELU, norm_layer=nn.LayerNorm):\\n\",\n    \"        super().__init__()\\n\",\n    \"        self.dim = dim\\n\",\n    \"        self.input_resolution = input_resolution\\n\",\n    \"        self.num_heads = num_heads\\n\",\n    \"        self.window_size = window_size\\n\",\n    \"        self.shift_size = shift_size\\n\",\n    \"        self.mlp_ratio = mlp_ratio\\n\",\n    \"        if min(self.input_resolution) <= self.window_size:\\n\",\n    \"            # if window size is larger than input resolution, we don't partition windows\\n\",\n    \"            self.shift_size = 0\\n\",\n    \"            self.window_size = min(self.input_resolution)\\n\",\n    \"        assert 0 <= self.shift_size < self.window_size, \\\"shift_size must in 0-window_size\\\"\\n\",\n    \"        \\n\",\n    \"        # create swin_attention_pattern sparsity pattern\\n\",\n    \"        attn_mask = AP.swin_attention_pattern(input_resolution[0], input_resolution[1], window_size, shift_size=shift_size)\\n\",\n    \"        attn_mask = SparseCS(attn_mask, torch.device(\\\"cuda\\\"))\\n\",\n    \"\\n\",\n    \"        self.norm1 = norm_layer(dim)\\n\",\n    \"        self.attn = Attention(\\n\",\n    \"            dim, window_size=(self.window_size, self.window_size), num_heads=num_heads,\\n\",\n    \"            qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop,\\n\",\n    \"            attn_mask=attn_mask\\n\",\n    \"        )\\n\",\n    \"\\n\",\n    \"        self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()\\n\",\n    \"        self.norm2 = norm_layer(dim)\\n\",\n    \"        mlp_hidden_dim = int(dim * mlp_ratio)\\n\",\n    \"        self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)\\n\",\n    \"\\n\",\n    \"    def forward(self, x):\\n\",\n    \"        H, W = self.input_resolution\\n\",\n    \"        B, L, C = x.shape\\n\",\n    \"        assert L == H * W, \\\"input feature has wrong size\\\"\\n\",\n    \"\\n\",\n    \"        shortcut = x\\n\",\n    \"        x = self.norm1(x)\\n\",\n    \"\\n\",\n    \"        # W-MSA/SW-MSA\\n\",\n    \"        x = self.attn(x)  # nW*B, window_size*window_size, C\\n\",\n    \"\\n\",\n    \"        # FFN\\n\",\n    \"        x = shortcut + self.drop_path(x)\\n\",\n    \"        x = x + self.drop_path(self.mlp(self.norm2(x)))\\n\",\n    \"\\n\",\n    \"        return x\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"e55578e0-1f4f-4a51-ab6c-eaa526e28b79\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's write a function that given a model, will replace all instances of timm.models.swin_transformer.SwinTransformerBlock with our own implementation, which leverages `scaled_dot_product_attention` and `swin_attention_pattern` from xformers\\n\",\n    \"\\n\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"id\": \"81e9c20f-69fb-48cc-b4e6-debb404e2240\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"def replace_attn_with_xformers_one(module):\\n\",\n    \"    module_output = module\\n\",\n    \"    if isinstance(module, timm.models.swin_transformer.SwinTransformerBlock):\\n\",\n    \"                \\n\",\n    \"        module_output = SwinTransformerBlock(module.dim, module.input_resolution, module.num_heads, module.window_size, module.shift_size, module.mlp_ratio)\\n\",\n    \"        module_output.drop_path = module.drop_path\\n\",\n    \"        module_output.norm1 = module.norm1\\n\",\n    \"        module_output.norm2 = module.norm2\\n\",\n    \"        module_output.mlp = module.mlp\\n\",\n    \"        \\n\",\n    \"        module_output.attn.qkv = module.attn.qkv\\n\",\n    \"        module_output.attn.attn_drop = module.attn.attn_drop\\n\",\n    \"        module_output.attn.proj = module.attn.proj\\n\",\n    \"        module_output.attn.proj_drop = module.attn.proj_drop\\n\",\n    \"        \\n\",\n    \"        module_output.train(module.training)\\n\",\n    \"    \\n\",\n    \"    else:\\n\",\n    \"\\n\",\n    \"        for name, child in module.named_children():\\n\",\n    \"            module_output.add_module(name, replace_attn_with_xformers_one(child))\\n\",\n    \"    del module\\n\",\n    \"    return module_output\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"12eaa142-ae39-4a8e-bad9-20b3738e38d2\",\n   \"metadata\": {},\n   \"source\": [\n    \"Now it's time to create our Swin Transformer. Nothing unusual here. Note that we will be keeping a copy of the model, which will be the model to use sparse self-attention\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"id\": \"d04d2cab-f1ec-4151-b41b-0c3406f0d9f7\",\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"model = timm.models.create_model(\\\"swin_base_patch4_window7_224\\\").cuda().eval()\\n\",\n    \"\\n\",\n    \"# zero relative positional embedding in original model as we don't implement it here\\n\",\n    \"for n, p in model.named_parameters():\\n\",\n    \"    if \\\"relative_position_bias_table\\\" in n:\\n\",\n    \"        torch.nn.init.zeros_(p)\\n\",\n    \"\\n\",\n    \"model_sparse = copy.deepcopy(model)\\n\",\n    \"model_sparse = replace_attn_with_xformers_one(model_sparse)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"42d3792a-c219-445e-bc24-e32d3d729a02\",\n   \"metadata\": {},\n   \"source\": [\n    \"Let's new create an input tensor verify if both the sparse and the baseline versions produce the same results\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"id\": \"52c4fce7-2e5a-4214-8944-cd54832f90f8\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Median absolute difference: 3.70e-05\\n\",\n      \"Max absolute difference:    2.51e-04\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"i = torch.rand(32, 3, 224, 224).cuda()\\n\",\n    \"\\n\",\n    \"with torch.no_grad():\\n\",\n    \"    r0 = model(i)\\n\",\n    \"    r1 = model_sparse(i)\\n\",\n    \"\\n\",\n    \"diff = (r0 - r1).abs()\\n\",\n    \"    \\n\",\n    \"print(f\\\"Median absolute difference: {diff.median().item():.2e}\\\")\\n\",\n    \"print(f\\\"Max absolute difference:    {diff.max().item():.2e}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6f7d83f9-b21b-48de-89dd-c255fef30f98\",\n   \"metadata\": {},\n   \"source\": [\n    \"The results are almost the same. The reason why they are not equivalent up to float precision is because we currently assume that the number of non-zero elements in the sparse matrix is a multiple of 4, so up to 3 elements in the self-attention might be dropped in order to satisfy this constraint.\\n\",\n    \"This constraint will be lifted in the future.\\n\",\n    \"\\n\",\n    \"Let's new benchmark both the sparse and the baseline versions\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"8ba0279a-ad14-4295-bb37-5778f60650dc\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Profiling the baseline (dense) model\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"id\": \"909b23f2-2ac7-488e-9447-089496d37346\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Forward only\\n\",\n      \"<torch.utils.benchmark.utils.common.Measurement object at 0x7f0dd83b4d60>\\n\",\n      \"profile\\n\",\n      \"  Median: 212.33 ms\\n\",\n      \"  IQR:    9.49 ms (210.94 to 220.43)\\n\",\n      \"  10 measurements, 1 runs per measurement, 1 thread\\n\",\n      \"Memory used: 1448.72509765625 MB\\n\",\n      \"\\n\",\n      \"Forward + backward\\n\",\n      \"<torch.utils.benchmark.utils.common.Measurement object at 0x7f0dd868abe0>\\n\",\n      \"profile\\n\",\n      \"  Median: 626.96 ms\\n\",\n      \"  IQR:    12.91 ms (623.15 to 636.06)\\n\",\n      \"  4 measurements, 1 runs per measurement, 1 thread\\n\",\n      \"Memory used: 8615.0703125 MB\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Forward only\\\")\\n\",\n    \"with torch.no_grad():\\n\",\n    \"    profile_model(lambda : model(i))\\n\",\n    \"print(\\\"\\\")\\n\",\n    \"print(\\\"Forward + backward\\\")\\n\",\n    \"profile_model(lambda : model(i).sum().backward())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"97bcc2c6-a2e7-403c-8b43-33c5f818a7c1\",\n   \"metadata\": {},\n   \"source\": [\n    \"### Profiling the sparse model\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"id\": \"4e17e1aa-6970-4b6f-9adb-4e51b55cb4ac\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Forward only\\n\",\n      \"<torch.utils.benchmark.utils.common.Measurement object at 0x7f0dd71b44c0>\\n\",\n      \"profile\\n\",\n      \"  Median: 208.51 ms\\n\",\n      \"  IQR:    1.29 ms (208.06 to 209.34)\\n\",\n      \"  10 measurements, 1 runs per measurement, 1 thread\\n\",\n      \"Memory used: 1636.5673828125 MB\\n\",\n      \"\\n\",\n      \"Forward + backward\\n\",\n      \"<torch.utils.benchmark.utils.common.Measurement object at 0x7f0dd83b4370>\\n\",\n      \"profile\\n\",\n      \"  Median: 607.60 ms\\n\",\n      \"  IQR:    9.11 ms (605.09 to 614.20)\\n\",\n      \"  4 measurements, 1 runs per measurement, 1 thread\\n\",\n      \"Memory used: 8770.02001953125 MB\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Forward only\\\")\\n\",\n    \"with torch.no_grad():\\n\",\n    \"    profile_model(lambda : model_sparse(i))\\n\",\n    \"print(\\\"\\\")\\n\",\n    \"print(\\\"Forward + backward\\\")\\n\",\n    \"profile_model(lambda : model_sparse(i).sum().backward())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"6cfa7340-8d7c-4be5-861b-2354324f50ca\",\n   \"metadata\": {},\n   \"source\": [\n    \"Those results indicate that the sparse model achieves the same speed as the manually-implemented dense version.\\n\",\n    \"This is very encouraging, as with a generic sparse implementation we are able to achieve comparable speed versus the optimized dense implementation, while being substantially simpler to implement (specially on the windows shift optimizations, see [\\\\[1\\\\]](https://github.com/microsoft/Swin-Transformer/issues/52) and [\\\\[2\\\\]](https://github.com/microsoft/Swin-Transformer/issues/38) for examples).\\n\",\n    \"\\n\",\n    \"From the memory perspective, the sparse model uses slightly more memory, as it needs to keep the indices of the non-zero elements in memory, while in the baseline dense model the structure is encoded directly in the code. Note that we can further reduce the memory needs by re-using the same sparse pattern over multiple layers.\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"id\": \"95caaf5c-53b4-493e-8a66-1ad09d1917f6\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Wrapping up\\n\",\n    \"\\n\",\n    \"In this notebook, we've shown that Swin Transformers can be casted as a sparse transformer, and we've shown that a generic implementation based on the sparse kernels from `xformers` is able to match performances compared to the hand-crafted implementation.\\n\",\n    \"\\n\",\n    \"We hope that this will further illustrate the power of custom sparsity patterns, and we hope xformers will enable new research directions on large sequences.\\n\",\n    \"\\n\",\n    \"Do not hesitate to reach out if you have questions.\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.8\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "docs/source/what_is_xformers.rst",
    "content": "What is xFormers?\n====================\n\nFlexible Transformers, defined by interoperable and optimized building blocks.\n\n.. image:: _static/logo.png\n    :width: 700px\n    :height: 205px\n    :align: center\n\n\nxFormers is focused on the following values\n\n- **Field agnostic**. This library is not focused on any given field, by design.\n\n- **Composable**. Ideally, break all the Transformer inspired models into a *block zoo*, which allows you to compose reference models but also study ablations or architecture search.\n\n- **Extensible**. xFormers aims at being *easy to extend locally*, so that one can focus on a specific improvement, and easily compare it against the state of the art.\n\n- **Optimized**. Reusing building blocks across domains means that engineering efforts can be more valued. And since you cannot improve what you cannot measure, xFormers is benchmark-heavy.\n\n- **Tested**. Each and every of the variant in the repo is tested, alone and composed with the other relevant blocks. This happens automatically anytime somebody proposes a new variant through a PR.\n\n- **Crowd Sourced**. PRs are really welcome, the state of the art is moving too fast for anything but a crowd sourced effort.\n"
  },
  {
    "path": "examples/llama_inference/README.md",
    "content": "# Llama inference\n\nThis example showcases how to use xformers kernels and cuda graphs to generate efficiently from large language models. The generation code works with both Llama2 and Code Llama (2023) models, but be aware that generating from large models will require more than a single GPU and a nightly build of PyTorch.\n\nExample runs:\n```console\n$ python -m generate --ckpt_dir models/CodeLlama-7b-Instruct/\nloaded SentencePiece model: #words: 32016 - bos id: 1 - eos id: 2\nloaded model in 12.36 seconds\n> [INST]abc[/INST]\nI'm not sure I understand what you are saying with \"abc\". Could you explain?\n---------------\n> [INST]can you write a hello world program in C#[/INST]\n Certainly! Here is a simple \"Hello, World!\" program in C#:\n` ` `\nusing System;\n\nclass HelloWorld\n{\n    static void Main(string[] args)\n    {\n        Console.WriteLine(\"Hello, World!\");\n    }\n}\n\n[...]\n\n$ # run a Code Llama model with model parallelism > 1, assuming you have two GPUs\n$ torchrun --nnodes 1 --nproc-per-node 2 -m generate --ckpt_dir models/CodeLlama-13b-Instruct/\n[...]\n```\n"
  },
  {
    "path": "examples/llama_inference/generate.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport json\nimport os\nimport readline  # type: ignore  # noqa\nimport sys\nimport time\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Iterable, Optional, Tuple, Union\n\nimport model as fast\nimport mp_utils\nimport sample_utils\nimport torch\nfrom stats import Stats\nfrom tokenizer import Tokenizer\n\nfrom xformers.ops.fmha.attn_bias import (\n    BlockDiagonalCausalWithOffsetPaddedKeysMask as AttnBias,\n)\n\n\n@dataclass\nclass GenArgs:\n    gen_length: int = 1000\n\n    use_sampling: bool = True\n    temperature: float = 0.6\n    top_p: float = 0.9\n\n\nclass FastGen:\n    GRAPH_WARMUPS: int = 3\n    tokenizer: Tokenizer\n\n    @staticmethod\n    def build(\n        ckpt_dir: str,\n        gen_args: GenArgs,\n        device: Union[torch.device, str],\n        tokenizer_path: Optional[str] = None,\n    ) -> \"FastGen\":\n        \"\"\"\n        Load a Llama or Code Llama checkpoint and return a new\n        generator for this model.\n        \"\"\"\n        start_time = time.time()\n        world_size = mp_utils.get_world_size()\n\n        checkpoints = sorted(Path(ckpt_dir).glob(\"*.pth\"))\n\n        assert len(checkpoints) > 0, f\"no checkpoint files in {ckpt_dir}\"\n        assert world_size == len(checkpoints), (\n            f\"checkpoint for model parallelism {len(checkpoints)}\"\n            f\" but world size is {world_size}\"\n        )\n\n        ckpt_path = checkpoints[mp_utils.get_rank()]\n        with open(Path(ckpt_dir) / \"params.json\", \"r\") as f:\n            params = json.loads(f.read())\n        model_args = fast.ModelArgs(**params)\n\n        if tokenizer_path is None:\n            tokenizer_path = str(Path(ckpt_dir) / \"tokenizer.model\")\n        if not os.path.isfile(tokenizer_path):\n            tokenizer_path = str(Path(ckpt_dir) / \"..\" / \"tokenizer.model\")\n        if not os.path.isfile(tokenizer_path):\n            raise RuntimeError(\"could not find the tokenizer model\")\n        tokenizer = Tokenizer(model_path=tokenizer_path)\n        model_args.vocab_size = tokenizer.n_words\n\n        torch.set_default_device(device)\n        torch.set_default_dtype(torch.bfloat16)\n\n        model = fast.Transformer(model_args)\n        checkpoint = torch.load(ckpt_path, map_location=\"cpu\", weights_only=True)\n        model.load_state_dict(checkpoint, strict=False)\n        print(f\"loaded model in {time.time() - start_time:.2f} seconds\")\n\n        return FastGen(gen_args, model_args, model, tokenizer)\n\n    def __init__(\n        self,\n        args: GenArgs,\n        model_args: fast.ModelArgs,\n        model: fast.Transformer,\n        tokenizer: Tokenizer,\n    ):\n        self.gen_args = args\n        self.model_args = model_args\n        self.model = model\n        self.tokenizer = tokenizer\n\n    @torch.inference_mode()\n    def generate_all(\n        self, prompts: list[list[int]], use_cuda_graphs: bool\n    ) -> Tuple[Stats, list[list[int]]]:\n        bs = len(prompts)\n        prompt_lens = [len(p) for p in prompts]\n        max_prompt_length = max(prompt_lens)\n        gen_length = self.gen_args.gen_length\n        max_seq_length = max_prompt_length + gen_length\n\n        cache = fast.make_cache(\n            args=self.model_args,\n            length=bs * max_seq_length,\n        )\n\n        bias = AttnBias.from_seqlens(\n            q_seqlen=prompt_lens,\n            kv_seqlen=prompt_lens,\n            kv_padding=max_seq_length,\n        )\n        bias.q_seqinfo.to(\"cuda\")\n        bias.k_seqinfo.to(\"cuda\")\n\n        graph = torch.cuda.CUDAGraph()\n\n        # Input tensors to the cuda graph\n        q_seqstart = bias.q_seqinfo.seqstart\n        kv_seqlen = bias.k_seqinfo.seqlen\n        tokens = torch.IntTensor(sum(prompts, [])).cuda()\n        out_tokens = torch.zeros((max_seq_length, bs), dtype=torch.int)\n\n        stats = Stats()\n        stats.phase(\"warmup\" if use_cuda_graphs else \"total\")\n\n        for niter in range(gen_length):\n            if niter <= self.GRAPH_WARMUPS or not use_cuda_graphs:\n                # Keep the first iteration out of the\n                # warmup, it processes prompts while all\n                # other iterations process sequences of 0\n                # or 1 token only\n                output = self.model.forward_with_attn_bias(\n                    token_values=tokens,\n                    attn_bias=bias,\n                    cache=cache,\n                )\n            elif niter == self.GRAPH_WARMUPS + 1:\n                recording_kwargs = {}\n                if \"capture_error_mode\" in torch.cuda.graph.__init__.__annotations__:\n                    # In PyTorch 2.1+ and nightlies from late Aug 2023,\n                    # we can do this to maybe avoid watchdog-related crashes\n                    recording_kwargs[\"capture_error_mode\"] = \"thread_local\"\n                with torch.cuda.graph(graph, **recording_kwargs):\n                    output = self.model.forward_with_attn_bias(\n                        token_values=tokens,\n                        attn_bias=bias,\n                        cache=cache,\n                    )\n                graph.replay()\n                # synchronize to get accurate timings\n                torch.cuda.synchronize()\n                stats.phase(\"graph\", tokens=(niter + 1) * bs)\n            else:\n                graph.replay()\n\n            # output: (sum(token_lengths), vocab_size)\n            logits = output.view(bs, self.model_args.vocab_size)\n\n            if self.gen_args.use_sampling:\n                temp = self.gen_args.temperature\n                top_p = self.gen_args.top_p\n                probs = torch.softmax(logits / temp, dim=-1)\n                next_token = sample_utils.top_p(probs, top_p)\n            else:\n                next_token = torch.argmax(logits, dim=-1)\n\n            next_token = next_token.reshape(bs)\n            out_tokens[niter, :] = next_token\n\n            # Update attention bias state for decoding rounds\n            if niter == 0:\n                q_seqstart.copy_(torch.arange(bs + 1, dtype=torch.int))\n                bias.q_seqinfo.min_seqlen = 1\n                bias.q_seqinfo.max_seqlen = 1\n                bias.q_seqinfo.seqstart_py = q_seqstart.tolist()\n                tokens = tokens[:bs]\n\n            kv_seqlen.add_(kv_seqlen < max_seq_length)\n\n            tokens.copy_(next_token)\n\n        stats.end_phase(tokens=gen_length * bs)\n\n        def trim_answer(prompt, tokens):\n            \"\"\"Trim the answer to end it on an eos token.\"\"\"\n            tokens = tokens[: max_seq_length - len(prompt)]\n            eos_id = self.tokenizer.eos_id\n            if eos_id in tokens:\n                return tokens[: tokens.index(eos_id) + 1]\n            else:\n                return tokens\n\n        answers = [\n            trim_answer(prompt, answer)\n            for prompt, answer in zip(prompts, out_tokens.t().tolist())\n        ]\n        return stats, answers\n\n\ndef get_prompts(interactive: bool) -> Iterable[list[str]]:\n    if interactive:\n        while True:\n            try:\n                prompts = input(\"enter prompt: \").split(\"\\n\")\n            except EOFError:\n                print(\"exiting\")\n                sys.exit(0)\n            yield prompts\n    else:\n        yield [\n            \"abc\",\n            \"can you write a hello world program in C#\",\n            \"peux tu resoudre le probleme des tours de Hanoi en ocaml\",\n        ]\n\n\ndef main(ckpt_dir: str, interactive: bool, add_instruction_tags: bool):\n    if \"WORLD_SIZE\" in os.environ:\n        mp_size = int(os.environ[\"WORLD_SIZE\"])\n        local_rank = int(os.environ[\"LOCAL_RANK\"])\n    else:\n        mp_size = 1\n        local_rank = 0\n\n    device = mp_utils.initialize(mp_size, local_rank)\n\n    g = FastGen.build(ckpt_dir, GenArgs(), device)\n\n    for prompts in get_prompts(interactive):\n        if add_instruction_tags:\n            prompts = [f\"[INST]{prompt}[/INST]\" for prompt in prompts]\n\n        tokens = [g.tokenizer.encode(x) for x in prompts]\n        stats, out_tokens = g.generate_all(\n            tokens, use_cuda_graphs=\"NO_CUDA_GRAPHS\" not in os.environ\n        )\n\n        if mp_utils.get_rank() == 0:\n            for i, prompt in enumerate(prompts):\n                print(f\"> {prompt}\")\n                answer = g.tokenizer.decode(out_tokens[i])\n                print(answer)\n                print(\"---------------\")\n\n            for phase_stats in stats.phases:\n                print(phase_stats.show())\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(\"Llama inference\")\n    parser.add_argument(\"ckpt_dir\")\n    parser.add_argument(\n        \"-i\", \"--interactive\", action=\"store_true\", help=\"ask for prompts\"\n    )\n    parser.add_argument(\n        \"--no-instruction-tags\", action=\"store_true\", help=\"do not add instruction tags\"\n    )\n\n    args = parser.parse_args()\n    main(\n        ckpt_dir=args.ckpt_dir,\n        interactive=args.interactive,\n        add_instruction_tags=not args.no_instruction_tags,\n    )\n"
  },
  {
    "path": "examples/llama_inference/model.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom dataclasses import dataclass\nfrom typing import Optional, Tuple, Union\n\nimport mp_utils\nimport torch\nfrom torch import nn\nfrom torch.nn import functional as F\n\nfrom xformers.ops import fmha, RMSNorm, rope_padded\nfrom xformers.ops.fmha.attn_bias import (\n    BlockDiagonalCausalWithOffsetPaddedKeysMask as AttnBias,\n)\n\n\n@dataclass\nclass ModelArgs:\n    dim: int = 512\n\n    n_layers: int = 8\n\n    n_heads: int = 8\n    n_kv_heads: Optional[int] = None\n\n    vocab_size: int = -1\n\n    ffn_dim_multiplier: Optional[float] = None\n\n    multiple_of: int = 256\n    \"\"\"\n    Enforces that the SwiGLU hidden layer size is a multiple\n    of large power of 2.\n    \"\"\"\n\n    norm_eps: float = 1e-5\n\n    rope_theta: float = 10000.0\n    \"\"\"\n    Positional encoding parameter; increase to 1e6 to run\n    Code Llama models with long contexts.\n    \"\"\"\n\n\nLayerCache = Tuple[torch.Tensor, torch.Tensor]\n\n\nclass Attention(nn.Module):\n    def __init__(\n        self,\n        dim: int,\n        head_dim: int,\n        n_heads: int,\n        n_kv_heads: int,\n        rope_theta: float,\n    ):\n        super().__init__()\n        mp_size = mp_utils.get_world_size()\n\n        self.head_dim = head_dim\n        self.rope_theta = rope_theta\n\n        self.n_local_heads = n_heads // mp_size\n        self.n_local_kv_heads = n_kv_heads // mp_size\n\n        self.wqkv = nn.Linear(\n            dim,\n            (self.n_local_heads + 2 * self.n_local_kv_heads) * head_dim,\n            bias=False,\n        )\n        self.wo = nn.Linear(\n            self.n_local_heads * head_dim,\n            dim,\n            bias=False,\n        )\n        self._register_load_state_dict_pre_hook(self.load_hook)\n\n    # This adapter makes sure we can load vanilla\n    # Llama checkpoints where wq, wk, and wv are\n    # not fused in a single parameter\n    def load_hook(\n        self,\n        state_dict,\n        prefix,\n        local_metadata,\n        strict,\n        missing_keys,\n        unexpected_keys,\n        error_msgs,\n    ):\n        if prefix + \"wq.weight\" in state_dict:\n            wq = state_dict.pop(prefix + \"wq.weight\")\n            wk = state_dict.pop(prefix + \"wk.weight\")\n            wv = state_dict.pop(prefix + \"wv.weight\")\n            state_dict[prefix + \"wqkv.weight\"] = torch.cat([wq, wk, wv])\n\n    def forward(\n        self,\n        x: torch.Tensor,\n        cache: LayerCache,\n        attn_bias: AttnBias,\n        position_index: Optional[torch.Tensor],\n    ) -> torch.Tensor:\n        # x.shape is (sum(seq_lens), dim)\n        #\n        # Since we support heterogenous sequence\n        # lengths, the hidden states are all\n        # concatenated together along the usual\n        # sequence dimension. The attention below\n        # finds out where sequences start & end\n        # using the provided attention bias.\n\n        xqkv = self.wqkv(x)\n        xq = xqkv[:, : (self.n_local_heads * self.head_dim)]\n        xkv = xqkv[:, (self.n_local_heads * self.head_dim) :]\n        xk, xv = xkv.chunk(2, 1)\n\n        output_shape = xq.shape\n        heads_per_group = self.n_local_heads // self.n_local_kv_heads\n        xq = xq.view(\n            1, xq.shape[0], self.n_local_kv_heads, heads_per_group, self.head_dim\n        )\n        xk = xk.view(1, xk.shape[0], self.n_local_kv_heads, 1, self.head_dim)\n        xv = xv.view(1, xv.shape[0], self.n_local_kv_heads, 1, self.head_dim)\n        cache_k, cache_v = cache\n\n        xq = rope_padded(\n            xq=xq,\n            xk=xk,\n            xv=xv,\n            cache_k=cache_k,\n            cache_v=cache_v,\n            attn_bias=attn_bias,\n            theta=self.rope_theta,\n        )\n\n        # rope_padded() updated the caches, so we\n        # call attention directly\n        output = fmha.memory_efficient_attention_forward(\n            xq, cache_k, cache_v, attn_bias\n        )\n        output = output.reshape(output_shape)\n        if position_index is not None:\n            output = output[position_index]\n        output = self.wo(output)\n        mp_utils.all_reduce(output)\n\n        return output\n\n\nclass FeedForward(nn.Module):\n    def __init__(\n        self,\n        dim: int,\n        hidden_dim: int,\n        multiple_of: int,\n        ffn_dim_multiplier: Optional[float],\n    ):\n        super().__init__()\n        mp_size = mp_utils.get_world_size()\n\n        hidden_dim = int(2 * hidden_dim / 3)\n        if ffn_dim_multiplier is not None:\n            hidden_dim = int(ffn_dim_multiplier * hidden_dim)\n        hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)\n        assert hidden_dim % mp_size == 0\n\n        self.w13 = nn.Linear(\n            dim,\n            2 * hidden_dim // mp_size,\n            bias=False,\n        )\n        self.w2 = nn.Linear(\n            hidden_dim // mp_size,\n            dim,\n            bias=False,\n        )\n        self._register_load_state_dict_pre_hook(self.load_hook)\n\n    # This adapter makes sure we can load vanilla\n    # Llama checkpoints where w1 and w3 are not\n    # fused in a single parameter\n    def load_hook(\n        self,\n        state_dict,\n        prefix,\n        local_metadata,\n        strict,\n        missing_keys,\n        unexpected_keys,\n        error_msgs,\n    ):\n        if prefix + \"w1.weight\" in state_dict:\n            w1 = state_dict.pop(prefix + \"w1.weight\")\n            w3 = state_dict.pop(prefix + \"w3.weight\")\n            state_dict[prefix + \"w13.weight\"] = torch.cat([w1, w3])\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        x13 = self.w13(x)\n        x1, x3 = x13.chunk(2, -1)\n        output = self.w2(F.silu(x1) * x3)\n        mp_utils.all_reduce(output)\n        return output\n\n\nclass TransformerBlock(nn.Module):\n    def __init__(self, args: ModelArgs, layer_index: int):\n        super().__init__()\n\n        assert args.dim % args.n_heads == 0\n        head_dim = args.dim // args.n_heads\n        if args.n_kv_heads is not None:\n            n_kv_heads = args.n_kv_heads\n        else:\n            n_kv_heads = args.n_heads\n\n        mp_size = mp_utils.get_world_size()\n        assert args.n_heads % n_kv_heads == 0\n        assert args.n_heads % mp_size == 0\n        assert n_kv_heads % mp_size == 0\n\n        self.is_last_layer = layer_index + 1 == args.n_layers\n\n        self.attention = Attention(\n            dim=args.dim,\n            head_dim=head_dim,\n            n_heads=args.n_heads,\n            n_kv_heads=n_kv_heads,\n            rope_theta=args.rope_theta,\n        )\n        self.feed_forward = FeedForward(\n            dim=args.dim,\n            hidden_dim=4 * args.dim,\n            multiple_of=args.multiple_of,\n            ffn_dim_multiplier=args.ffn_dim_multiplier,\n        )\n        self.attention_norm = RMSNorm(args.dim, eps=args.norm_eps)\n        self.ffn_norm = RMSNorm(args.dim, eps=args.norm_eps)\n\n    def forward(\n        self,\n        x: torch.Tensor,\n        cache: LayerCache,\n        attn_bias: AttnBias,\n    ) -> torch.Tensor:\n        position_index = None\n        if self.is_last_layer and attn_bias.q_seqinfo.max_seqlen > 1:\n            position_index = attn_bias.q_seqinfo.seqstart[1:] - 1\n\n        h = self.attention.forward(\n            self.attention_norm(x),\n            cache,\n            attn_bias,\n            position_index=position_index,\n        )\n        if position_index is not None:\n            x = x[position_index]\n        h = h + x\n        out = h + self.feed_forward(self.ffn_norm(h))\n        return out\n\n\nclass Transformer(nn.Module):\n    def __init__(self, args: ModelArgs):\n        super().__init__()\n        mp_size = mp_utils.get_world_size()\n        assert args.dim % mp_size == 0\n        assert args.vocab_size > 0\n        assert args.vocab_size % mp_size == 0\n\n        self.tok_embeddings = nn.Embedding(\n            num_embeddings=args.vocab_size,\n            embedding_dim=args.dim // mp_size,\n        )\n\n        self.layers = nn.ModuleList()\n        for layer_index in range(args.n_layers):\n            self.layers.append(TransformerBlock(args, layer_index))\n\n        self.norm = RMSNorm(args.dim, eps=args.norm_eps)\n\n        self.output = nn.Linear(\n            args.dim,\n            args.vocab_size // mp_size,\n            bias=False,\n        )\n\n    @torch.no_grad()\n    def forward_with_attn_bias(\n        self,\n        token_values: torch.Tensor,\n        attn_bias: AttnBias,\n        cache: list[LayerCache],\n    ) -> torch.Tensor:\n        h_parallel = self.tok_embeddings(token_values)\n        h = mp_utils.all_gather(h_parallel)\n\n        for i, layer in enumerate(self.layers):\n            h = layer(h, cache[i], attn_bias)\n\n        logits_parallel = self.output(self.norm(h))\n        logits = mp_utils.all_gather(logits_parallel)\n        return logits.float()\n\n    def forward(\n        self,\n        token_values: torch.Tensor,\n        token_lengths: torch.Tensor,\n        start_pos: torch.Tensor,\n        cache: list[LayerCache],\n        kv_padding: int,\n    ) -> torch.Tensor:\n        attn_bias = AttnBias.from_seqlens(\n            q_seqlen=token_lengths.tolist(),\n            kv_seqlen=(start_pos + token_lengths).tolist(),\n            kv_padding=kv_padding,\n        )\n        return self.forward_with_attn_bias(token_values, attn_bias, cache)\n\n\ndef make_cache(\n    args: ModelArgs,\n    length: int,\n    device: Optional[Union[str, torch.device]] = None,\n    n_layers: Optional[int] = None,\n    dtype: Optional[torch.dtype] = None,\n) -> list[LayerCache]:\n    \"\"\"\n    Allocate a cache to be used with the Transformer module.\n\n    Args:\n        args (ModelArgs): the model configuration.\n        length (int): per layer cache size.\n            It is usually budgeted as ``max_batch * max_seq``\n        device (torch.device, optional): the device on which\n            the cache should be allocated.\n        n_layers (int, optional): the number of layers to\n            allocate a cache for (defaults to the model\n            settings).\n        dtype (torch.dtype, optional): the dtype to use for\n            cache entries (defaults to the default dtype).\n\n    Returns:\n        The cache object to pass to ``Tranformer.forward``.\n    \"\"\"\n\n    head_dim = args.dim // args.n_heads\n    n_kv_heads = args.n_kv_heads\n    if n_kv_heads is None:\n        n_kv_heads = args.n_heads\n    n_local_kv_heads = n_kv_heads // mp_utils.get_world_size()\n\n    if n_layers is None:\n        n_layers = args.n_layers\n\n    shape = (1, length, n_local_kv_heads, 1, head_dim)\n    heads_per_group = args.n_heads // n_kv_heads\n    expansion = (-1, -1, -1, heads_per_group, -1)\n    return [\n        (\n            torch.zeros(shape, device=device, dtype=dtype).expand(expansion),\n            torch.zeros(shape, device=device, dtype=dtype).expand(expansion),\n        )\n        for _ in range(n_layers)\n    ]\n\n\ndef cache_prefix(cache: list[LayerCache], length: int) -> list[LayerCache]:\n    \"\"\"\n    Take a prefix view of a larger cache.\n\n    The original cache object remains of identical size and valid\n    after the shrinked alias has been used. This function is useful\n    when a cache was allocated for a larger batch size than what is\n    necessary.\n\n    Args:\n        cache: the cache to take a view in.\n        length (int): the desired length\n\n    Returns:\n        A view in the input cache object.\n    \"\"\"\n\n    if len(cache) > 0:\n        assert cache[0][0].shape[1] >= length\n\n    return [(ck[:, :length], cv[:, :length]) for ck, cv in cache]\n"
  },
  {
    "path": "examples/llama_inference/mp_utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport functools\nimport os\nfrom typing import Optional\n\nimport torch\nfrom torch.distributed import ProcessGroup\n\n_GROUP: Optional[ProcessGroup] = None\n_WORLD_SIZE: Optional[int] = None\n_LOCAL_RANK: int = 0\n\n\ndef initialize(\n    world_size: int,\n    local_rank: int,\n    group: Optional[ProcessGroup] = None,\n    use_gpu: bool = True,\n    seed: int = 80486,\n) -> str:\n    \"\"\"\n    Initialize model parallelism support.\n\n    Args:\n        world_size (int): the number of processes running on\n            the current node available for model parallelism.\n        local_rank (int): the present process' rank.\n        group (torch.distributed.ProcessGroup, optional): the\n            process group to use for model parallel communications.\n        use_gpu (bool, optional): whether computations are\n            happening on a GPU or not (defaults to True).\n        seed (int, optional): the seed used to seed the prng\n            on all model parallel processes\n\n    Returns\n        The pytorch device to use in the present process.\n\n    Note:\n        If ``group`` is not specified, the default process group is\n        used for model parallelism. This means that the present\n        module may be incompatible with other forms of parallelism\n        such as data parallelism.\n    \"\"\"\n    global _GROUP\n    global _WORLD_SIZE\n    global _LOCAL_RANK\n\n    assert local_rank < world_size\n\n    if use_gpu:\n        device = f\"cuda:{local_rank}\"\n        torch.cuda.set_device(local_rank)\n    else:\n        device = \"cpu\"\n\n    if group is None:\n        if \"MASTER_ADDR\" not in os.environ:\n            assert world_size == 1\n            os.environ[\"MASTER_ADDR\"] = \"127.0.0.1\"\n            os.environ[\"MASTER_PORT\"] = \"1234\"\n\n        torch.distributed.init_process_group(\n            backend=\"nccl\" if use_gpu else \"gloo\",\n            init_method=\"env://\",\n            world_size=world_size,\n            rank=local_rank,\n        )\n\n    _GROUP = group\n    _WORLD_SIZE = world_size\n    _LOCAL_RANK = local_rank\n\n    torch.manual_seed(seed)\n\n    return device\n\n\n@functools.cache\ndef get_world_size() -> int:\n    if _WORLD_SIZE is None:\n        raise RuntimeError(\"model parallelism was not initialized\")\n    return _WORLD_SIZE\n\n\n@functools.cache\ndef get_rank() -> int:\n    if _WORLD_SIZE is None:\n        raise RuntimeError(\"model parallelism was not initialized\")\n    return _LOCAL_RANK\n\n\ndef all_gather(x: torch.Tensor) -> torch.Tensor:\n    \"\"\"\n    Gather a tensor of shape (n, m) into a tensor of shape (n, mp_size * m).\n    \"\"\"\n\n    mp_size = get_world_size()\n    if mp_size == 1:\n        return x\n\n    gather = [torch.empty_like(x) for _ in range(mp_size)]\n    torch.distributed.all_gather(gather, x, group=_GROUP)\n    return torch.cat(gather, dim=-1)\n\n\ndef all_reduce(x: torch.Tensor):\n    if get_world_size() > 1:\n        # reduce with a sum\n        torch.distributed.all_reduce(x, group=_GROUP)\n"
  },
  {
    "path": "examples/llama_inference/requirements.txt",
    "content": "sentencepiece\ntorch>=2.2.0\nxformers>=0.0.22\n"
  },
  {
    "path": "examples/llama_inference/sample_utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport torch\n\n\ndef top_p(probs: torch.Tensor, p: float) -> torch.Tensor:\n    \"\"\"\n    Perform top-p (nucleus) sampling on a probability distribution.\n\n    Args:\n        probs (torch.Tensor): probability distribution tensor.\n        p (float): probability threshold for top-p sampling.\n\n    Returns:\n        torch.Tensor: sampled token indices.\n\n    Note:\n        Top-p sampling selects the smallest set of tokens whose cumulative\n        probability mass exceeds the threshold p. The distribution is\n        renormalized based on the selected tokens.\n    \"\"\"\n    probs_sort, probs_idx = torch.sort(probs, dim=-1, descending=True)\n    probs_sum = torch.cumsum(probs_sort, dim=-1)\n    mask = probs_sum - probs_sort > p\n    probs_sort[mask] = 0.0\n    next_token = torch.multinomial(probs_sort, num_samples=1)\n    next_token = torch.gather(probs_idx, -1, next_token)\n    return next_token\n"
  },
  {
    "path": "examples/llama_inference/stats.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport time\nfrom dataclasses import dataclass\nfrom typing import Optional\n\n\n@dataclass\nclass PhaseStats:\n    name: str\n    tokens: int\n    time: float\n\n    def show(self) -> str:\n        tps = self.tokens / self.time\n        return (\n            f\"[{self.name}] \"\n            f\"generated tokens: {self.tokens}\"\n            f\" - total time: {self.time:.3f}s\"\n            f\" - {tps:.1f} tokens per second\"\n        )\n\n\nclass Stats:\n    \"\"\"\n    Generation stats, split by phases.\n    \"\"\"\n\n    def __init__(self):\n        self.phases = []\n        self.current = None\n\n    def end_phase(self, tokens: int, now: Optional[float] = None):\n        \"\"\"Terminate the current phase.\"\"\"\n        if self.current is None:\n            return\n        if now is None:\n            now = time.time()\n        cname, ctokens, ctime = self.current\n        stats = PhaseStats(\n            name=cname,\n            tokens=tokens - ctokens,\n            time=now - ctime,\n        )\n        self.phases.append(stats)\n\n    def phase(self, name: str, tokens: int = 0):\n        \"\"\"\n        Start a new phase, and terminate the current one,\n        if one is ongoing.\n        \"\"\"\n        now = time.time()\n        self.end_phase(tokens, now)\n        self.current = (name, tokens, now)\n"
  },
  {
    "path": "examples/llama_inference/tokenizer.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport os\n\nfrom sentencepiece import SentencePieceProcessor\n\n\nclass Tokenizer:\n    \"\"\"Encoding/decoding text using SentencePiece.\"\"\"\n\n    def __init__(self, model_path: str):\n        \"\"\"\n        Initializes the Tokenizer with a SentencePiece model.\n\n        Args:\n            model_path (str): The path to the SentencePiece model file.\n        \"\"\"\n        assert os.path.isfile(model_path), model_path\n        self.sp_model = SentencePieceProcessor(model_file=model_path)\n\n        self.n_words: int = self.sp_model.vocab_size()\n        self.bos_id: int = self.sp_model.bos_id()\n        self.eos_id: int = self.sp_model.eos_id()\n        self.pad_id: int = self.sp_model.pad_id()\n        print(\n            f\"loaded SentencePiece model: \"\n            f\"#words: {self.n_words} - \"\n            f\"bos id: {self.bos_id} - \"\n            f\"eos id: {self.eos_id}\"\n        )\n        assert self.sp_model.vocab_size() == self.sp_model.get_piece_size()\n\n    def encode(self, s: str, bos: bool = True, eos: bool = False) -> list[int]:\n        \"\"\"\n        Encodes a string into a list of token IDs.\n\n        Args:\n            s (str): The input string to be encoded.\n            bos (bool): Whether to prepend the beginning-of-sequence token.\n            eos (bool): Whether to append the end-of-sequence token.\n\n        Returns:\n            list[int]: A list of token IDs.\n        \"\"\"\n        assert type(s) is str\n        t = self.sp_model.encode(s)\n        if bos:\n            t = [self.bos_id] + t\n        if eos:\n            t = t + [self.eos_id]\n        return t\n\n    def decode(self, t: list[int]) -> str:\n        \"\"\"\n        Decodes a list of token IDs into a string.\n\n        Args:\n            t (list[int]): The list of token IDs to be decoded.\n\n        Returns:\n            str: The decoded string.\n        \"\"\"\n        return self.sp_model.decode(t)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\n# XXX: If your project needs other packages to build properly, add them to this list.\nrequires = [\"setuptools >= 64\", \"torch >= 2.10\"]\nbuild-backend = \"setuptools.build_meta\"\n\n[tool.black]\ntarget-version = [\"py310\"]\nline-length = 88\n\n[tool.usort]\nfirst_party_detection = false\n"
  },
  {
    "path": "requirements-benchmark.txt",
    "content": "# Get core deps\n-r requirements-test.txt\n\n# Example requirement, can be anything that pip knows\n# install with `pip install -r requirements.txt`, and make sure that CI does the same\ntqdm >= 4.66.3\npandas == 2.2.2\nseaborn == 0.13.2\npytorch-lightning >= 1.3\ntorchmetrics>=0.7.0, <0.10.1\n"
  },
  {
    "path": "requirements-test.txt",
    "content": "# Get core deps.\n-r requirements.txt\n\n\n# Tools for static checking.\nblack==26.3.1\nstdlibs==2024.1.28\nufmt==2.8.0\nusort==1.0.8.post1\nflake8 == 6.1.0\nflake8-copyright\nmypy == 1.10.0\npyre-check == 0.9.16\npyre-extensions == 0.0.29\n\n# Tools for unit tests & coverage.\npytest == 7.2.0\npytest-cov == 2.10.0\npytest-timeout == 1.4.2\npytest-random-order == 1.1.1\n\n# Dependency for Mixture of Experts\nfairscale >= 0.4.5\nscipy >= 1.7\n\n# Dependency for fused layers, optional\ncmake\n"
  },
  {
    "path": "requirements.txt",
    "content": "# Example requirement, can be anything that pip knows\n# install with `pip install -r requirements.txt`, and make sure that CI does the same\ntorch >= 2.10\nnumpy\n"
  },
  {
    "path": "setup.cfg",
    "content": "[flake8]\nmax-line-length = 140\nextend-ignore = E203, W503\n"
  },
  {
    "path": "setup.py",
    "content": "#!/usr/bin/env python3\n\n# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport datetime\nimport distutils.command.clean\nimport glob\nimport importlib.util\nimport json\nimport os\nimport platform\nimport shlex\nimport shutil\nimport subprocess\nimport sys\nfrom pathlib import Path\nfrom typing import List, Optional\n\nimport setuptools\nimport torch\nfrom torch.utils.cpp_extension import (\n    BuildExtension,\n    CppExtension,\n    CUDA_HOME,\n    CUDAExtension,\n    ROCM_HOME,\n)\n\ntry:\n    from wheel.bdist_wheel import bdist_wheel as _bdist_wheel\nexcept ImportError:\n    _bdist_wheel = None\n\nthis_dir = os.path.dirname(__file__)\npt_attn_compat_file_path = os.path.join(\n    this_dir, \"xformers\", \"ops\", \"fmha\", \"torch_attention_compat.py\"\n)\n\n# Define the module name\nmodule_name = \"torch_attention_compat\"\n\n# Load the module\nspec = importlib.util.spec_from_file_location(module_name, pt_attn_compat_file_path)\nassert spec is not None\nattn_compat_module = importlib.util.module_from_spec(spec)\nsys.modules[module_name] = attn_compat_module\nassert spec.loader is not None\nspec.loader.exec_module(attn_compat_module)\n\n\ndef get_extra_nvcc_flags_for_build_type(cuda_version: int) -> List[str]:\n    build_type = os.environ.get(\"XFORMERS_BUILD_TYPE\", \"RelWithDebInfo\").lower()\n    if build_type == \"relwithdebinfo\":\n        if cuda_version >= 1201 and cuda_version < 1202:\n            print(\n                \"Looks like we are using CUDA 12.1 which segfaults when provided with\"\n                \" the -generate-line-info flag. Disabling it.\"\n            )\n            return []\n        return [\"--generate-line-info\"]\n    elif build_type == \"release\":\n        return []\n    elif build_type == \"debug\":\n        return [\"--device-debug\"]\n    else:\n        raise ValueError(f\"Unknown build type: {build_type}\")\n\n\ndef fetch_requirements():\n    with open(\"requirements.txt\") as f:\n        reqs = f.read().strip().split(\"\\n\")\n    return reqs\n\n\ndef get_local_version_suffix() -> str:\n    if not (Path(__file__).parent / \".git\").is_dir():\n        # Most likely installing from a source distribution\n        return \"\"\n    date_suffix = datetime.datetime.now().strftime(\"%Y%m%d\")\n    git_hash = subprocess.check_output(\n        [\"git\", \"rev-parse\", \"--short\", \"HEAD\"], cwd=Path(__file__).parent\n    ).decode(\"ascii\")[:-1]\n    return f\"+{git_hash}.d{date_suffix}\"\n\n\ndef generate_version_py(version: str) -> str:\n    content = \"# noqa: C801\\n\"\n    content += f'__version__ = \"{version}\"\\n'\n    tag = os.getenv(\"GIT_TAG\")\n    if tag is not None:\n        content += f'git_tag = \"{tag}\"\\n'\n    return content\n\n\ndef get_cuda_version(cuda_dir) -> int:\n    nvcc_bin = \"nvcc\" if cuda_dir is None else cuda_dir + \"/bin/nvcc\"\n    raw_output = subprocess.check_output([nvcc_bin, \"-V\"], universal_newlines=True)\n    output = raw_output.split()\n    release_idx = output.index(\"release\") + 1\n    release = output[release_idx].split(\".\")\n    bare_metal_major = int(release[0])\n    bare_metal_minor = int(release[1][0])\n\n    assert bare_metal_minor < 100\n    return bare_metal_major * 100 + bare_metal_minor\n\n\ndef get_hip_version(rocm_dir) -> Optional[str]:\n    hipcc_bin = \"hipcc\" if rocm_dir is None else os.path.join(rocm_dir, \"bin\", \"hipcc\")\n    try:\n        raw_output = subprocess.check_output(\n            [hipcc_bin, \"--version\"], universal_newlines=True\n        )\n    except Exception as e:\n        print(\n            f\"hip installation not found: {e} ROCM_PATH={os.environ.get('ROCM_PATH')}\"\n        )\n        return None\n    for line in raw_output.split(\"\\n\"):\n        if \"HIP version\" in line:\n            return line.split()[-1]\n    return None\n\n\ndef rename_cpp_cu(cpp_files):\n    for entry in cpp_files:\n        shutil.copy(entry, os.path.splitext(entry)[0] + \".cu\")\n\n\ndef get_extensions():\n    extensions_dir = os.path.join(\"xformers\", \"csrc\")\n\n    sources = glob.glob(os.path.join(extensions_dir, \"**\", \"*.cpp\"), recursive=True)\n    source_cuda = glob.glob(os.path.join(extensions_dir, \"**\", \"*.cu\"), recursive=True)\n    fmha_source_cuda = glob.glob(\n        os.path.join(extensions_dir, \"**\", \"fmha\", \"**\", \"*.cu\"), recursive=True\n    )\n    exclude_files = [\"small_k.cu\", \"decoder.cu\", \"attention_cutlass_rand_uniform.cu\"]\n    fmha_source_cuda = [\n        c\n        for c in fmha_source_cuda\n        if not any(exclude_file in c for exclude_file in exclude_files)\n    ]\n\n    source_hip = glob.glob(\n        os.path.join(extensions_dir, \"attention\", \"hip_*\", \"**\", \"*.cpp\"),\n        recursive=True,\n    )\n\n    source_hip_generated = glob.glob(\n        os.path.join(extensions_dir, \"attention\", \"hip_*\", \"**\", \"*.cu\"),\n        recursive=True,\n    )\n    # avoid the temporary .cu files generated under xformers/csrc/attention/hip_fmha\n    source_cuda = list(set(source_cuda) - set(source_hip_generated))\n    sources = list(set(sources) - set(source_hip))\n\n    xformers_pt_cutlass_attn = os.getenv(\"XFORMERS_PT_CUTLASS_ATTN\")\n    # By default, we try to link to torch internal CUTLASS attention implementation\n    # and silently switch to local CUTLASS attention build if no compatibility\n    # If we force 'torch CUTLASS switch' then setup will fail when no compatibility\n    if (\n        xformers_pt_cutlass_attn is None or xformers_pt_cutlass_attn == \"1\"\n    ) and attn_compat_module.is_pt_cutlass_compatible(\n        force=xformers_pt_cutlass_attn == \"1\"\n    ):\n        source_cuda = list(set(source_cuda) - set(fmha_source_cuda))\n\n    if \"XFORMERS_SELECTIVE_BUILD\" in os.environ:\n        pattern = os.environ[\"XFORMERS_SELECTIVE_BUILD\"]\n        source_cuda = [f for f in source_cuda if pattern in str(f)]\n\n    cutlass_dir = os.path.join(this_dir, \"third_party\", \"cutlass\", \"include\")\n    cutlass_util_dir = os.path.join(\n        this_dir, \"third_party\", \"cutlass\", \"tools\", \"util\", \"include\"\n    )\n    cutlass_examples_dir = os.path.join(this_dir, \"third_party\", \"cutlass\", \"examples\")\n    if not os.path.exists(cutlass_dir):\n        raise RuntimeError(\n            f\"CUTLASS submodule not found at {cutlass_dir}. \"\n            \"Did you forget to run \"\n            \"`git submodule update --init --recursive` ?\"\n        )\n\n    extension = CppExtension\n\n    define_macros = []\n\n    extra_compile_args = {\"cxx\": [\"-O3\", \"-std=c++17\"]}\n    if sys.platform == \"win32\":\n        if os.getenv(\"DISTUTILS_USE_SDK\") == \"1\":\n            extra_compile_args = {\"cxx\": [\"-O2\", \"/std:c++17\"]}\n        define_macros += [(\"xformers_EXPORTS\", None)]\n        extra_compile_args[\"cxx\"].extend(\n            [\"/MP\", \"/Zc:lambda\", \"/Zc:preprocessor\", \"/Zc:__cplusplus\"]\n        )\n    elif \"OpenMP not found\" not in torch.__config__.parallel_info():\n        extra_compile_args[\"cxx\"].append(\"-fopenmp\")\n\n    include_dirs = [extensions_dir]\n    ext_modules = []\n    cuda_version = None\n    hip_version = None\n\n    if (\n        (\n            torch.cuda.is_available()\n            and (CUDA_HOME is not None)\n            and (torch.version.cuda is not None)\n        )\n        or os.getenv(\"FORCE_CUDA\", \"0\") == \"1\"\n        or os.getenv(\"TORCH_CUDA_ARCH_LIST\", \"\") != \"\"\n    ):\n        cuda_version = get_cuda_version(CUDA_HOME)\n        extension = CUDAExtension\n        sources += source_cuda\n        if cuda_version < 1205:\n            # swiglu_fairinternal.cu uses cuda::ptx::cp_async_bulk which requires\n            # CUDA 12.5\n            sources.remove(os.path.join(extensions_dir, \"swiglu_fairinternal.cu\"))\n        include_dirs += [\n            cutlass_dir,\n            cutlass_util_dir,\n            cutlass_examples_dir,\n        ]\n        nvcc_flags = [\n            \"-DHAS_PYTORCH\",\n            \"--use_fast_math\",\n            \"-U__CUDA_NO_HALF_OPERATORS__\",\n            \"-U__CUDA_NO_HALF_CONVERSIONS__\",\n            \"--extended-lambda\",\n            \"-D_ENABLE_EXTENDED_ALIGNED_STORAGE\",\n            \"-std=c++17\",\n        ] + get_extra_nvcc_flags_for_build_type(cuda_version)\n        if os.getenv(\"XFORMERS_ENABLE_DEBUG_ASSERTIONS\", \"0\") != \"1\":\n            nvcc_flags.append(\"-DNDEBUG\")\n        nvcc_flags += shlex.split(os.getenv(\"NVCC_FLAGS\", \"\"))\n        if cuda_version >= 1102:\n            nvcc_flags += [\n                \"--threads\",\n                \"4\",\n                \"--ptxas-options=-v\",\n            ]\n        if sys.platform == \"win32\":\n            nvcc_flags += [\n                \"-Xcompiler\",\n                \"/Zc:lambda\",\n                \"-Xcompiler\",\n                \"/Zc:preprocessor\",\n                \"-Xcompiler\",\n                \"/Zc:__cplusplus\",\n            ]\n        extra_compile_args[\"nvcc\"] = nvcc_flags\n\n        # For now we enforce the PyTorch stable ABI only for CUDA builds (not HIP).\n        stable_args = [\n            \"-DTORCH_STABLE_ONLY\",\n            \"-DTORCH_TARGET_VERSION=0x020a000000000000\",\n        ]\n        extra_compile_args[\"cxx\"].extend(stable_args)\n        extra_compile_args[\"nvcc\"].extend(stable_args + [\"-DUSE_CUDA\"])\n\n        if (\n            \"--device-debug\" not in nvcc_flags and \"-G\" not in nvcc_flags\n        ):  # (incompatible with -G)\n            extra_compile_args[\"nvcc\"] += [\n                # Workaround for a regression with nvcc > 11.6\n                # See https://github.com/facebookresearch/xformers/issues/712\n                \"--ptxas-options=-O2\",\n                \"--ptxas-options=-allow-expensive-optimizations=true\",\n            ]\n    elif (\n        torch.version.hip\n        and os.getenv(\"XFORMERS_CK_FLASH_ATTN\", \"1\") == \"1\"\n        and (torch.cuda.is_available() or os.getenv(\"HIP_ARCHITECTURES\", \"\") != \"\")\n    ):\n        rename_cpp_cu(source_hip)\n        hip_version = get_hip_version(ROCM_HOME)\n\n        source_hip_cu = []\n        for ff in source_hip:\n            source_hip_cu += [ff.replace(\".cpp\", \".cu\")]\n\n        extension = CUDAExtension\n        sources += source_hip_cu\n        include_dirs += [\n            Path(this_dir) / \"xformers\" / \"csrc\" / \"attention\" / \"hip_fmha\",\n            Path(this_dir) / \"xformers\" / \"csrc\" / \"attention\" / \"hip_decoder\",\n        ]\n\n        include_dirs += [\n            Path(this_dir) / \"third_party\" / \"composable_kernel_tiled\" / \"include\"\n        ]\n\n        cc_flag = [\"-DBUILD_PYTHON_PACKAGE\"]\n        use_rtn_bf16_convert = os.getenv(\"ENABLE_HIP_FMHA_RTN_BF16_CONVERT\", \"0\")\n        if use_rtn_bf16_convert == \"1\":\n            cc_flag += [\"-DCK_TILE_FLOAT_TO_BFLOAT16_DEFAULT=3\"]\n\n        arch_list = os.getenv(\"HIP_ARCHITECTURES\", \"native\").split()\n\n        offload_compress_flag = []\n        if hip_version >= \"6.2.\":\n            offload_compress_flag = [\"--offload-compress\"]\n\n        extra_compile_args[\"nvcc\"] = [\n            \"-O3\",\n            \"-std=c++17\",\n            *[f\"--offload-arch={arch}\" for arch in arch_list],\n            *offload_compress_flag,\n            \"-U__CUDA_NO_HALF_OPERATORS__\",\n            \"-U__CUDA_NO_HALF_CONVERSIONS__\",\n            \"-DCK_TILE_FMHA_FWD_FAST_EXP2=1\",\n            \"-fgpu-flush-denormals-to-zero\",\n            \"-Werror\",\n            \"-Wc++11-narrowing\",\n            \"-Woverloaded-virtual\",\n            \"-mllvm\",\n            \"-enable-post-misched=0\",\n            \"-mllvm\",\n            \"-amdgpu-early-inline-all=true\",\n            \"-mllvm\",\n            \"-amdgpu-function-calls=false\",\n            \"-mllvm\",\n            \"-greedy-reverse-local-assignment=1\",\n        ] + cc_flag\n\n    ext_modules.append(\n        extension(\n            \"xformers._C\",\n            sorted(sources),\n            include_dirs=[os.path.abspath(p) for p in include_dirs],\n            define_macros=define_macros,\n            extra_compile_args=extra_compile_args,\n        )\n    )\n\n    return ext_modules, {\n        \"version\": {\n            \"cuda\": cuda_version,\n            \"hip\": hip_version,\n            \"torch\": torch.__version__,\n            \"python\": platform.python_version(),\n        },\n        \"env\": {\n            k: os.environ.get(k)\n            for k in [\n                \"TORCH_CUDA_ARCH_LIST\",\n                \"PYTORCH_ROCM_ARCH\",\n                \"XFORMERS_BUILD_TYPE\",\n                \"XFORMERS_ENABLE_DEBUG_ASSERTIONS\",\n                \"NVCC_FLAGS\",\n                \"XFORMERS_PACKAGE_FROM\",\n            ]\n        },\n    }\n\n\nclass clean(distutils.command.clean.clean):  # type: ignore\n    def run(self):\n        if os.path.exists(\".gitignore\"):\n            with open(\".gitignore\", \"r\") as f:\n                ignores = f.read()\n                for wildcard in filter(None, ignores.split(\"\\n\")):\n                    for filename in glob.glob(wildcard):\n                        try:\n                            os.remove(filename)\n                        except OSError:\n                            shutil.rmtree(filename, ignore_errors=True)\n\n        # It's an old-style class in Python 2.7...\n        distutils.command.clean.clean.run(self)\n\n\nclass bdist_wheel_abi_none(_bdist_wheel if _bdist_wheel else object):  # type: ignore[misc]\n    \"\"\"\n    Custom wheel builder that tags wheels as ABI-independent despite containing compiled code.\n    The compiled extensions are plain shared libraries (.so/.dll) that use only PyTorch's\n    TORCH_LIBRARY mechanism, with no Python C API dependencies. This allows the same wheel\n    to work across different Python versions and variants (including free-threaded builds).\n    \"\"\"\n\n    def get_tag(self):\n        if _bdist_wheel is None:\n            raise RuntimeError(\"wheel package is required to build wheels\")\n\n        # Get the default tags from parent class\n        python_tag, abi_tag, plat_tag = super().get_tag()\n\n        # Override ABI tag to 'none' since our .so files have no Python ABI dependency\n        # Use 'py39' as python tag to indicate minimum Python version (3.9+)\n        # Keep platform tag since we have platform-specific compiled code\n        return \"py39\", \"none\", plat_tag\n\n\nclass BuildExtensionWithExtraFiles(BuildExtension):\n    def __init__(self, *args, **kwargs) -> None:\n        self.xformers_build_metadata = kwargs.pop(\"extra_files\")\n        self.pkg_name = \"xformers\"\n        super().__init__(*args, **kwargs)\n\n    def get_export_symbols(self, ext):\n        # Don't export PyInit_* symbols since our extension doesn't use the\n        # Python C API. It registers operators with PyTorch via\n        # STABLE_TORCH_LIBRARY_FRAGMENT and is loaded via torch.ops.load_library().\n        return []\n\n    def build_extensions(self) -> None:\n        super().build_extensions()\n\n        for filename, content in self.xformers_build_metadata.items():\n            with open(\n                os.path.join(self.build_lib, self.pkg_name, filename), \"w+\"\n            ) as fp:\n                fp.write(content)\n\n    def copy_extensions_to_source(self) -> None:\n        \"\"\"\n        Used for `pip install -e .`\n        Copies everything we built back into the source repo\n        \"\"\"\n        build_py = self.get_finalized_command(\"build_py\")\n        package_dir = build_py.get_package_dir(self.pkg_name)\n\n        for filename in self.xformers_build_metadata.keys():\n            inplace_file = os.path.join(package_dir, filename)\n            regular_file = os.path.join(self.build_lib, self.pkg_name, filename)\n            self.copy_file(regular_file, inplace_file, level=self.verbose)\n        super().copy_extensions_to_source()\n\n    def get_ext_filename(self, ext_name):\n        # Return plain .so/.pyd names without Python version tags\n        # This creates ABI-independent binaries that work with any Python version\n        ext_path = ext_name.split(\".\")\n        ext_basename = ext_path[-1]\n        ext_dir = os.path.join(*ext_path[:-1]) if len(ext_path) > 1 else \"\"\n\n        if sys.platform == \"win32\":\n            # Windows: use .pyd extension (required for importlib to find it)\n            filename = f\"{ext_basename}.pyd\"\n        else:\n            # Linux/Mac: use plain .so extension\n            filename = f\"{ext_basename}.so\"\n\n        return os.path.join(ext_dir, filename) if ext_dir else filename\n\n\nif __name__ == \"__main__\":\n    if os.getenv(\"BUILD_VERSION\"):  # In CI\n        version = os.getenv(\"BUILD_VERSION\", \"0.0.0\")\n    else:\n        version_txt = os.path.join(this_dir, \"version.txt\")\n        with open(version_txt) as f:\n            version = f.readline().strip()\n        version += get_local_version_suffix()\n\n    extensions, extensions_metadata = get_extensions()\n    setuptools.setup(\n        name=\"xformers\",\n        description=\"XFormers: A collection of composable Transformer building blocks.\",\n        version=version,\n        install_requires=fetch_requirements(),\n        packages=setuptools.find_packages(exclude=(\"tests*\", \"benchmarks*\")),\n        ext_modules=extensions,\n        cmdclass={\n            \"build_ext\": BuildExtensionWithExtraFiles.with_options(\n                no_python_abi_suffix=True,\n                extra_files={\n                    \"cpp_lib.json\": json.dumps(extensions_metadata),\n                    \"version.py\": generate_version_py(version),\n                },\n            ),\n            \"bdist_wheel\": bdist_wheel_abi_none,\n            \"clean\": clean,\n        },\n        url=\"https://facebookresearch.github.io/xformers/\",\n        python_requires=\">=3.9\",\n        author=\"Facebook AI Research\",\n        author_email=\"oncall+xformers@xmail.facebook.com\",\n        long_description=\"XFormers: A collection of composable Transformer building blocks.\"\n        + \"XFormers aims at being able to reproduce most architectures in the Transformer-family SOTA,\"\n        + \"defined as compatible and combined building blocks as opposed to monolithic models\",\n        long_description_content_type=\"text/markdown\",\n        classifiers=[\n            \"Programming Language :: Python :: 3.9\",\n            \"Programming Language :: Python :: 3.10\",\n            \"Programming Language :: Python :: 3.11\",\n            \"Programming Language :: Python :: 3.12\",\n            \"License :: OSI Approved :: BSD License\",\n            \"Topic :: Scientific/Engineering :: Artificial Intelligence\",\n            \"Operating System :: OS Independent\",\n        ],\n        zip_safe=False,\n    )\n"
  },
  {
    "path": "stubs/fvcore/nn.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/matplotlib/pyplot.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/numpy/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import (\n    Any,\n    Container,\n    Generic,\n    Iterable,\n    Optional,\n    overload,\n    Sized,\n    SupportsAbs,\n    SupportsBytes,\n    SupportsComplex,\n    SupportsFloat,\n    SupportsInt,\n    Tuple,\n    Type,\n    TypeVar,\n    Union,\n)\n\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom typing_extensions import Literal\n\nDType = TypeVar(\"DType\")\nNewDType = TypeVar(\"NewDType\")\nTs = TypeVarTuple(\"Ts\")\nTs2 = TypeVarTuple(\"Ts2\")\n\nN = TypeVar(\"N\", bound=int)\nA1 = TypeVar(\"A1\")\nA2 = TypeVar(\"A2\")\n\nclass _ArrayOrScalarCommon(\n    Generic[DType, Unpack[Ts]],\n    SupportsInt,\n    SupportsFloat,\n    SupportsComplex,\n    SupportsBytes,\n    SupportsAbs[Any],\n): ...\nclass float: ...\n\nclass ndarray(_ArrayOrScalarCommon[DType, Unpack[Ts]], Iterable, Sized, Container):\n    def __init__(\n        self,\n        shape: Tuple[Unpack[Ts]],\n        dtype: Type[DType] = ...,\n        buffer=...,\n        offset: Optional[int] = ...,\n        strides: Tuple[int, ...] = ...,\n        order: Optional[str] = ...,\n    ) -> None: ...\n    @overload\n    def __getitem__(\n        self: ndarray[DType, A1, A2], key: Literal[0]\n    ) -> ndarray[DType, A2]: ...\n    @overload\n    def __getitem__(\n        self: ndarray[DType, A1, A2], key: Literal[1]\n    ) -> ndarray[DType, A1]: ...\n    def __setitem__(self, key, value): ...\n    @property\n    def shape(self) -> Tuple[Unpack[Ts]]: ...\n    @overload\n    def reshape(self, shape: Tuple[Unpack[Ts2]]) -> ndarray[DType, Unpack[Ts2]]: ...\n    @overload\n    def reshape(self, *shape: Unpack[Ts2]) -> ndarray[DType, Unpack[Ts2]]: ...\n    def __add__(self, other) -> ndarray[DType, Unpack[Ts]]: ...\n    def __div__(self, other) -> ndarray[DType, Unpack[Ts]]: ...\n    def __truediv__(self, other) -> ndarray[DType, Unpack[Ts]]: ...\n    # ===== BEGIN `astype` =====\n    @overload\n    def astype(self, dtype: Type[NewDType]) -> ndarray[NewDType, Unpack[Ts]]: ...\n    @overload\n    def astype(self, dtype: Literal[\"int64\"]) -> ndarray[int64, Unpack[Ts]]: ...\n    @overload\n    def astype(self, dtype: Literal[\"float32\"]) -> ndarray[float32, Unpack[Ts]]: ...\n    @overload\n    def astype(self, dtype: Literal[\"float64\"]) -> ndarray[float64, Unpack[Ts]]: ...\n    # ===== END `astype` =====\n\n# ===== BEGIN `empty` =====\n# `shape` as tuple, dtype=\"int64\"\n@overload\ndef empty(\n    shape: Tuple[Unpack[Ts]], dtype: Literal[\"int64\"]\n) -> ndarray[int64, Unpack[Ts]]: ...\n\n# `shape` as tuple, dtype as e.g. np.float32\n@overload\ndef empty(\n    shape: Tuple[Unpack[Ts]], dtype: Type[DType]\n) -> ndarray[DType, Unpack[Ts]]: ...\n\n# `shape` as integer, dtype as e.g. np.float32\n@overload\ndef empty(shape: N, dtype: Type[DType]) -> ndarray[DType, N]: ...\n\n# ===== END `empty` =====\ndef array(\n    object: object,\n    dtype: Type[DType] = ...,\n    copy: bool = ...,\n    subok: bool = ...,\n    ndmin: int = ...,\n) -> ndarray[DType, Unpack[Tuple[Any, ...]]]: ...\ndef sin(x: ndarray[DType, Unpack[Ts]]) -> ndarray[DType, Unpack[Ts]]: ...\n\nclass int64:\n    def __init__(self, value=...): ...\n\nclass float32:\n    def __init__(self, value=...): ...\n\nclass float64:\n    def __init__(self, value=...): ...\n\nloadtxt: Any\nasarray: Any\nzeros: Any\n"
  },
  {
    "path": "stubs/pandas.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/recommonmark/transform.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/seaborn.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/sklearn/model_selection.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/submitit.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/tensorflow.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport builtins\nfrom typing import (\n    Any,\n    Callable,\n    Dict,\n    Generic,\n    Iterable,\n    List,\n    NamedTuple,\n    Optional,\n    overload,\n    Sequence,\n    Tuple,\n    Type,\n    TypeVar,\n    Union,\n)\n\nfrom numpy import ndarray\nfrom pyre_extensions import (\n    Add,\n    Broadcast,\n    Divide,\n    Multiply,\n    Product,\n    TypeVarTuple,\n    Unpack,\n)\nfrom typing_extensions import Literal as L\n\nfrom . import nn as nn, sparse as sparse\nfrom .autograd import *\nfrom .random import initial_seed, set_rng_state\n\nDType = TypeVar(\"DType\")\nDType2 = TypeVar(\"DType2\")\nLayout = TypeVar(\"Layout\")\nWild = TypeVar(\"Wild\")\n\nT = TypeVar(\"T\")\nTs = TypeVarTuple(\"Ts\")\nRs = TypeVarTuple(\"Rs\")\nRs2 = TypeVarTuple(\"Rs2\")\nQs = TypeVarTuple(\"Qs\")\nN = TypeVar(\"N\", bound=int)\nM = TypeVar(\"M\", bound=int)\nB = TypeVar(\"B\", bound=int)\nP = TypeVar(\"P\", bound=int)\nR = TypeVar(\"R\", bound=int)\nN1 = TypeVar(\"N1\", bound=int)\nN2 = TypeVar(\"N2\", bound=int)\nN3 = TypeVar(\"N3\", bound=int)\nN4 = TypeVar(\"N4\", bound=int)\nN5 = TypeVar(\"N5\", bound=int)\nN6 = TypeVar(\"N6\", bound=int)\n\nbuiltin_bool = builtins.bool\nbuiltin_float = builtins.float\n\n# These are torch's datatypes, which have the same names as the builtins.\nclass complex64: ...\nclass complex128: ...\nclass float16: ...\nclass float32: ...\nclass float64: ...\nclass int64: ...\nclass int32: ...\nclass bool: ...\nclass memory_format: ...\n\nint = int32\nfloat = float32\ndouble = float64\n\nclass long: ...\nclass layout: ...\n\nstrided: layout = ...\n\nNumber = Union[builtins.int, builtins.float, builtins.bool]\n\nclass MaxNamedTuple(Generic[DType, Unpack[Ts]]):\n    values: Tensor[DType, Unpack[Ts]]\n    indices: Tensor[int64, Unpack[Ts]]\n    @overload\n    def __getitem__(self, key: L[0]) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def __getitem__(self, key: L[1]) -> Tensor[int64, Unpack[Ts]]: ...\n\nclass device:\n    def __init__(self, device_str: str): ...\n\n_just_device = device\n_device = Union[device, str]\n\nclass Size(Tuple[builtins.int, ...]):\n    @overload\n    def __getitem__(self: Size, key: builtins.int) -> builtins.int: ...\n    @overload\n    def __getitem__(self: Size, key: slice) -> Size: ...\n    def numel(self: Size) -> builtins.int: ...\n\nclass Generator(object):\n    device: _device\n    def __init__(self, device: Union[_device, str, None] = None) -> None: ...\n    def get_state(self) -> Tensor: ...\n    def set_state(self, _new_state: Tensor) -> Generator: ...\n    def manual_seed(self, seed: builtins.int) -> Generator: ...\n    def seed(self) -> builtins.int: ...\n    def initial_seed(self) -> builtins.int: ...\n\ndefault_generator: Generator = ...\n\nclass Storage(object):\n    _cdata: int\n    def __deepcopy__(self, memo) -> \"Storage\": ...\n    def _new_shared(self, int) -> \"Storage\": ...\n    def _write_file(\n        self, f: Any, is_real_file: builtins.bool, save_size: builtins.bool\n    ) -> None: ...\n    def element_size(self) -> int: ...\n    def is_shared(self) -> bool: ...\n    def share_memory_(self) -> \"Storage\": ...\n    def size(self) -> int: ...\n\nclass Tensor(Generic[DType, Unpack[Ts]]):\n    requires_grad: builtins.bool\n    data: Tensor[DType, Unpack[Ts]]\n    names: List[str]\n    layout: layout\n    T: Tensor[DType, Unpack[Ts]]\n    output_nr: builtins.int\n    _version: builtins.int\n    _base: Optional[Tensor]\n    _cdata: builtins.int\n    grad_fn: Any\n    grad: Optional[Tensor]\n    _grad_fn: Any\n    _grad: Optional[Tensor]\n    _backward_hooks: Optional[Dict[builtins.int, Callable[[Tensor], Optional[Tensor]]]]\n    @overload\n    def __init__(self, other: Tensor[DType, Unpack[Ts]]) -> None: ...\n    @overload\n    def __init__(\n        self, *args: Unpack[Ts], device: Union[_device, str, None] = ...\n    ) -> None: ...\n    @overload\n    def __init__(self, storage: Storage) -> None: ...\n    @overload\n    def __init__(\n        self, size: Tuple[Unpack[Ts]], *, device: Union[_device, str, None] = ...\n    ) -> None: ...\n    @property\n    def device(self) -> _device: ...\n    @property\n    def dtype(self) -> Type[DType]: ...\n    def long(self) -> \"LongTensor[DType, Unpack[Ts]]\": ...\n    # BEWARE: The type for self must not reuse `Ts`. This is because the type\n    # of the object is `Tensor[DType, Unpack[Ts]]`.\n    # We are trying to match part of it by using fresh type variables N1 and\n    # Rs: `self: Tensor[DType, N1, Unpack[Rs]]`.\n    # If we used Ts, then `Ts` would be the one from the object type. We would\n    # be saying that the object type `Tensor[DType, Unpack[Ts]]` must match\n    # `Tensor[DType, N1, Unpack[Ts]]`, which is absurd.\n    @overload\n    def size(self: Tensor[DType, N1, Unpack[Rs]], axis: L[0]) -> N1: ...\n    @overload\n    def size(self: Tensor[DType, N1, N2, Unpack[Rs]], axis: L[1]) -> N2: ...\n    @overload\n    def size(self: Tensor[DType, Unpack[Rs], N1], axis: L[-1]) -> N1: ...\n    @overload\n    def size(self: Tensor[DType, Unpack[Rs], N1, N2], axis: L[-2]) -> N1: ...\n    @overload\n    def size(self: Tensor[DType, Unpack[Rs]]) -> Tuple[Unpack[Rs]]: ...\n    @overload\n    def split(\n        self: Tensor[DType, N1, Unpack[Rs]], split_size_or_sections: N, dim: L[0] = ...\n    ) -> Iterable[Tensor[DType, N, Unpack[Rs]]]: ...\n    @overload\n    def split(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        split_size_or_sections: N,\n        dim: L[1] = ...,\n    ) -> Iterable[Tensor[DType, N1, N, Unpack[Rs]]]: ...\n    @overload\n    def item(self: Tensor[DType, L[1]]) -> DType: ...\n    @overload\n    def item(self: Tensor[DType]) -> DType: ...\n    def numel(self) -> builtins.int: ...\n    def backward(self) -> None: ...\n    @overload\n    def __getitem__(\n        self: Tensor[DType, N, Unpack[Rs]], item: L[0]\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def __getitem__(\n        self: Tensor[DType, Unpack[Rs]], item: None\n    ) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n    @overload\n    def __getitem__(\n        self: Tensor[DType, Unpack[Rs]], item: Tensor[bool, Unpack[Rs]]\n    ) -> Tensor[DType, builtins.int]: ...\n    @overload\n    def __getitem__(self, item: Any) -> Any: ...\n    @overload\n    def expand(\n        self: Tensor[DType, Unpack[Rs]], sizes: Tuple[Unpack[Rs2]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def expand(\n        self: Tensor[DType, Unpack[Rs]], *sizes: Unpack[Rs2]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    def detach(self: T) -> T: ...\n    # pyre-ignore[24]: Pyre is unable to find the custom stubs for numpy.\n    def numpy(self) -> ndarray[DType, Unpack[Ts]]: ...\n    shape: Tuple[Unpack[Ts]]\n    ndim: builtins.int\n    @overload\n    def to(\n        self: Tensor[DType, Unpack[Rs]], dtype: Type[T], device: _device = ...\n    ) -> Tensor[T, Unpack[Rs]]: ...\n    @overload\n    def to(\n        self: Tensor[DType, Unpack[Rs]], device: _device\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    device: _just_device\n    @overload\n    def __add__(\n        self: Tensor[DType, Unpack[Rs]], other: Tensor[DType, Unpack[Rs2]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __add__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Rs]]: ...\n    @overload\n    def __iadd__(\n        self, other: Tensor[DType, Unpack[Rs]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __iadd__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __radd__(\n        self: Tensor[DType, Unpack[Rs]], other: Tensor[DType, Unpack[Rs2]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __radd__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Rs]]: ...\n    @overload\n    def __sub__(\n        self: Tensor[DType, Unpack[Rs]], other: Tensor[DType, Unpack[Rs2]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __sub__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Rs]]: ...\n    @overload\n    def __isub__(\n        self, other: Tensor[DType, Unpack[Rs]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __isub__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __rsub__(\n        self: Tensor[DType, Unpack[Rs]], other: Tensor[DType, Unpack[Rs2]]\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __rsub__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Rs]]: ...\n    @overload\n    def __mul__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: Tensor[DType, Unpack[Rs2]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __mul__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __imul__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __imul__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __rmul__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: Tensor[DType, Unpack[Rs2]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __rmul__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __pow__(\n        self: Tensor[DType, Unpack[Rs]],\n        other: Tensor[DType, Unpack[Rs2]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __pow__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __truediv__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __truediv__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[float32, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __itruediv__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def __itruediv__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __rtruediv__(\n        self,\n        other: builtin_float,\n    ) -> Tensor[float32, Unpack[Ts]]: ...\n    @overload\n    def __floordiv__(\n        self,\n        other: builtins.int,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def __floordiv__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __ifloordiv__(\n        self,\n        other: builtins.int,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def __ifloordiv__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __rfloordiv__(\n        self,\n        other: builtins.int,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    def __invert__(self) -> Tensor[DType, Unpack[Ts]]: ...\n    def __neg__(self) -> Tensor[DType, Unpack[Ts]]: ...\n    def __iand__(\n        self: Tensor[bool, Unpack[Rs]],\n        other: Tensor[bool, Unpack[Rs2]],\n    ) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    def __and__(\n        self: Tensor[bool, Unpack[Rs]],\n        other: Tensor[bool, Unpack[Rs2]],\n    ) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Rs2]]]]]: ...\n    @overload\n    def __matmul__(\n        self: Tensor[DType, N1],\n        other: Tensor[DType, N1],\n    ) -> Tensor[DType]: ...\n    @overload\n    def __matmul__(\n        self: Tensor[DType, Unpack[Rs], N1, N2],\n        other: Tensor[DType, Unpack[Qs], N2, N3],\n    ) -> Tensor[\n        DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Qs]]]], N1, N3\n    ]: ...\n    def __ne__(\n        self: Tensor[DType, Unpack[Rs]], other: DType\n    ) -> Tensor[bool, Unpack[Rs]]: ...\n    def abs(self) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def all(\n        self: Tensor[DType, Unpack[Ts]],\n    ) -> Tensor[bool, L[1]]: ...\n    @overload\n    def all(\n        self: Tensor[DType, N, Unpack[Ts]],\n        dim: L[0],\n    ) -> Tensor[bool, Unpack[Ts]]: ...\n    @overload\n    def all(\n        self: Tensor[DType, N1, N2, Unpack[Ts]],\n        dim: L[1],\n    ) -> Tensor[bool, N1, Unpack[Ts]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[True],\n    ) -> LongTensor[int64, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[True],\n    ) -> LongTensor[int64, N1, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, N1, Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[True],\n    ) -> LongTensor[int64, N1, N2, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[True],\n    ) -> LongTensor[int64, Unpack[Rs], L[1]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, Unpack[Rs]]: ...\n    @overload\n    def argmax(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: L[None] = ...,\n        keepdim: builtins.bool = ...,\n    ) -> LongTensor[int64]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[True],\n    ) -> LongTensor[int64, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[True],\n    ) -> LongTensor[int64, N1, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, N1, Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[True],\n    ) -> LongTensor[int64, N1, N2, L[1], Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[True],\n    ) -> LongTensor[int64, Unpack[Rs], L[1]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[False] = ...,\n    ) -> LongTensor[int64, Unpack[Rs]]: ...\n    @overload\n    def argmin(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: L[None] = ...,\n        keepdim: builtins.bool = ...,\n    ) -> LongTensor[int64]: ...\n    # Note: Not defining this as a method `def bool()` because that confuses\n    # Pyre in other method signatures that use `torch.bool`. Not sure why.\n    bool: Callable[[], Tensor[bool, Unpack[Ts]]] = ...\n    @overload\n    def chunk(self: Tensor[DType, Unpack[Rs], N], chunks: L[2], dim: L[-1]) -> Tuple[\n        Tensor[DType, Unpack[Rs], Divide[N, L[2]]],\n        Tensor[DType, Unpack[Rs], Divide[N, L[2]]],\n    ]: ...\n    @overload\n    def chunk(\n        self: Tensor[DType, N, Unpack[Rs]], chunks: L[2], dim: L[0] = ...\n    ) -> Tuple[\n        Tensor[DType, Divide[N, L[2]], Unpack[Rs]],\n        Tensor[DType, Divide[N, L[2]], Unpack[Rs]],\n    ]: ...\n    def clone(\n        input, *, memory_format: Optional[memory_format] = ...\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def count_nonzero(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n    ) -> Tensor[int64, Unpack[Rs]]: ...\n    @overload\n    def count_nonzero(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n    ) -> Tensor[int64, N1, Unpack[Rs]]: ...\n    @overload\n    def count_nonzero(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n    ) -> Tensor[int64, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def count_nonzero(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n    ) -> Tensor[int64, Unpack[Rs]]: ...\n    @overload\n    def count_nonzero(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: L[None] = ...,\n        keepdim: builtins.bool = ...,\n    ) -> Tensor[int64]: ...\n    @overload\n    def dim(self: Tensor[DType]) -> L[0]: ...\n    @overload\n    def dim(self: Tensor[DType, builtins.int]) -> L[1]: ...\n    @overload\n    def dim(self: Tensor[DType, builtins.int, builtins.int]) -> L[2]: ...\n    @overload\n    def dim(self: Tensor[DType, builtins.int, builtins.int, builtins.int]) -> L[3]: ...\n    def half(\n        self: Tensor[DType, Unpack[Rs]], memory_format: Optional[memory_format] = ...\n    ) -> Tensor[float16, Unpack[Rs]]: ...\n    def is_contiguous(\n        self, memory_format: Optional[memory_format] = ...\n    ) -> builtins.bool: ...\n    def indices(self) -> Tensor: ...\n    is_cuda: builtins.bool\n    def masked_select(self, mask: Tensor, *, out: Optional[Tensor] = ...) -> Tensor: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[True],\n    ) -> MaxNamedTuple[DType, L[1], Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[False] = ...,\n    ) -> MaxNamedTuple[DType, Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[True],\n    ) -> MaxNamedTuple[DType, N1, L[1], Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[False] = ...,\n    ) -> MaxNamedTuple[DType, N1, Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[True],\n    ) -> MaxNamedTuple[DType, N1, N2, L[1], Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[False] = ...,\n    ) -> MaxNamedTuple[DType, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[True],\n    ) -> MaxNamedTuple[DType, Unpack[Rs], L[1]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[False] = ...,\n    ) -> MaxNamedTuple[DType, Unpack[Rs]]: ...\n    @overload\n    def max(\n        self: Tensor[DType, Unpack[Rs], N1, N2],\n        dim: L[-2],\n        keepdim: L[True],\n    ) -> MaxNamedTuple[DType, Unpack[Rs], L[1], N2]: ...\n    @overload\n    def max(\n        self: Tensor[DType, Unpack[Rs], N1, N2],\n        dim: L[-2],\n        keepdim: L[False] = ...,\n    ) -> MaxNamedTuple[DType, Unpack[Rs], N2]: ...\n    @overload\n    def max(\n        self: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[DType]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[True],\n    ) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        keepdim: L[False] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[True],\n    ) -> Tensor[DType, N1, L[1], Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        keepdim: L[False] = ...,\n    ) -> Tensor[DType, N1, Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[True],\n    ) -> Tensor[DType, N1, N2, L[1], Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        dim: L[2],\n        keepdim: L[False] = ...,\n    ) -> Tensor[DType, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[True],\n    ) -> Tensor[DType, Unpack[Rs], L[1]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, Unpack[Rs], N1],\n        dim: L[-1],\n        keepdim: L[False] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def mean(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: L[None] = ...,\n        keepdim: builtins.bool = ...,\n    ) -> Tensor[DType]: ...\n    def bitwise_not(self) -> Tensor[DType, Unpack[Ts]]: ...\n    def bitwise_not_(self) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def diff(\n        self: Tensor[DType, Unpack[Rs], Add[N1, L[1]], N2],\n        dim: L[-2],\n    ) -> Tensor[DType, Unpack[Rs], N1, N2]: ...\n    @overload\n    def diff(\n        self: Tensor[DType, Add[N, L[1]], Unpack[Rs]],\n        dim: L[0],\n    ) -> Tensor[DType, N, Unpack[Rs]]: ...\n    @overload\n    def diff(\n        self: Tensor[DType, N1, Add[N2, L[1]], Unpack[Rs]],\n        dim: L[1],\n    ) -> Tensor[DType, N1, N2, Unpack[Rs]]: ...\n    @overload\n    def diff(\n        self: Tensor[DType, Unpack[Rs], Add[N, L[1]]],\n        dim: L[-1] = ...,\n    ) -> Tensor[DType, Unpack[Rs], N]: ...\n    def is_sparse(self) -> builtins.bool: ...\n    def coalesce(self: Tensor[DType, Unpack[Rs]]) -> Tensor[DType, Unpack[Rs]]: ...\n    def values(self: Tensor[DType, Unpack[Rs]]) -> Tensor[DType, Unpack[Rs]]: ...\n    def to_sparse(self: Tensor[DType, Unpack[Ts]]) -> Tensor[DType, Unpack[Ts]]: ...\n    # Note: Not defining this as a method `def float()` because that confuses\n    # Pyre in other method signatures that use `torch.float`. Not sure why.\n    float: Callable[[], Tensor[float32, Unpack[Ts]]] = ...\n    @overload\n    def __eq__(\n        self,\n        other: Tensor[DType, Unpack[Rs]],\n    ) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def __eq__(\n        self,\n        other: object,\n    ) -> Tensor[bool, Unpack[Ts]]: ...\n    def argsort(\n        self, dim: builtins.int = ..., descending: builtin_bool = ...\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    def bmm(\n        self: Tensor[DType, B, N, M], mat2: Tensor[DType, B, M, P]\n    ) -> Tensor[DType, B, N, P]: ...\n    def diag_embed(\n        self: Tensor[DType, Unpack[Rs], N],\n    ) -> Tensor[DType, Unpack[Rs], N, N]: ...\n    @overload\n    def matmul(\n        self: Tensor[DType, N1],\n        other: Tensor[DType, N1],\n    ) -> Tensor[DType]: ...\n    @overload\n    def matmul(\n        self: Tensor[DType, Unpack[Rs], N1, N2],\n        other: Tensor[DType, Unpack[Qs], N2, N3],\n    ) -> Tensor[\n        DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Qs]]]], N1, N3\n    ]: ...\n    def multinomial(\n        self: Tensor[DType, Unpack[Rs], N1],\n        num_samples: N2,\n        replacement: builtins.bool = ...,\n        *,\n        generator: Optional[Generator] = ...,\n    ) -> Tensor[DType, Unpack[Rs], N2]: ...\n    @overload\n    def new_ones(\n        self,\n        size: Tuple[Unpack[Rs]],\n        dtype: Type[DType2],\n        device: _device = ...,\n        requires_grad: builtins.bool = ...,\n    ) -> Tensor[DType2, Unpack[Rs]]: ...\n    @overload\n    def new_ones(\n        self,\n        size: Tuple[Unpack[Rs]],\n        dtype: Type[DType] = ...,\n        device: _device = ...,\n        requires_grad: builtins.bool = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def unsqueeze(\n        self: Tensor[DType, Unpack[Rs]], dim: L[-1]\n    ) -> Tensor[DType, Unpack[Rs], L[1]]: ...\n    @overload\n    def unsqueeze(\n        self: Tensor[DType, Unpack[Rs]], dim: L[0]\n    ) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n    @overload\n    def unsqueeze(\n        self: Tensor[DType, N, Unpack[Rs]], dim: L[1]\n    ) -> Tensor[DType, N, L[1], Unpack[Rs]]: ...\n    @overload\n    def unsqueeze(\n        self: Tensor[DType, N1, N2, Unpack[Rs]], dim: L[2]\n    ) -> Tensor[DType, N1, N2, L[1], Unpack[Rs]]: ...\n    @overload\n    def unsqueeze_(\n        self: Tensor[DType, Unpack[Rs]], dim: L[-1]\n    ) -> Tensor[DType, Unpack[Rs], L[1]]: ...\n    @overload\n    def unsqueeze_(\n        self: Tensor[DType, Unpack[Rs]], dim: L[0]\n    ) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n    @overload\n    def unsqueeze_(\n        self: Tensor[DType, N, Unpack[Rs]], dim: L[1]\n    ) -> Tensor[DType, N, L[1], Unpack[Rs]]: ...\n    @overload\n    def unsqueeze_(\n        self: Tensor[DType, N1, N2, Unpack[Rs]], dim: L[2]\n    ) -> Tensor[DType, N1, N2, L[1], Unpack[Rs]]: ...\n    @property\n    def real(self: Tensor[complex64, Unpack[Rs]]) -> Tensor[float32, Unpack[Rs]]: ...\n    @overload\n    def repeat(\n        self: Tensor[DType, N1], size1: N2\n    ) -> Tensor[DType, Multiply[N1, N2]]: ...\n    @overload\n    def repeat(\n        self: Tensor[DType, N1, N2], size1: N3, size2: N4\n    ) -> Tensor[DType, Multiply[N1, N3], Multiply[N2, N4]]: ...\n    @overload\n    def repeat(\n        self: Tensor[DType, N1, N2, N3], size1: N4, size2: N5, size3: N6\n    ) -> Tensor[DType, Multiply[N1, N4], Multiply[N2, N5], Multiply[N3, N6]]: ...\n    @overload\n    def repeat_interleave(\n        self: Tensor[DType, Unpack[Rs], N1], repeats: N, dim: L[-1]\n    ) -> Tensor[DType, Unpack[Rs], Multiply[N1, N]]: ...\n    @overload\n    def repeat_interleave(\n        self: Tensor[DType, N1, Unpack[Rs]], repeats: N, dim: L[0]\n    ) -> Tensor[DType, Multiply[N1, N], Unpack[Rs]]: ...\n    @overload\n    def repeat_interleave(\n        self: Tensor[DType, N1, N2, Unpack[Rs]], repeats: N, dim: L[1]\n    ) -> Tensor[DType, N1, Multiply[N2, N], Unpack[Rs]]: ...\n    @overload\n    def repeat_interleave(\n        self: Tensor[DType, Unpack[Rs]], repeats: N, dim: L[None] = ...\n    ) -> Tensor[DType, Product[N, Unpack[Rs]]]: ...\n    # The output shape here depends on the contents of `repeats`, so give up.\n    @overload\n    def repeat_interleave(\n        input: Tensor[DType, Unpack[Rs]], repeats: Tensor, dim: builtins.int = ...\n    ) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\n    def __setitem__(self, item: object, other: object) -> None: ...\n    @overload\n    def scatter(\n        self,\n        dim: builtins.int,\n        index: Tensor,\n        src: Union[Tensor, float],\n        reduce: Optional[str] = ...,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def scatter_(\n        self,\n        dim: builtins.int,\n        index: Tensor,\n        src: Union[Tensor, float],\n        reduce: Optional[str] = ...,\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def softmax(self, dim: builtins.int) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def softmax(\n        self, dim: builtins.int, dtype: Type[DType2]\n    ) -> Tensor[DType2, Unpack[Ts]]: ...\n    @overload\n    def stride(\n        self: Tensor[DType, builtins.int, Unpack[Rs]], dim: L[0]\n    ) -> Product[Unpack[Rs]]: ...\n    @overload\n    def stride(\n        self: Tensor[DType, builtins.int, builtins.int, Unpack[Rs]], dim: L[1]\n    ) -> Product[Unpack[Rs]]: ...\n    @overload\n    def stride(\n        self: Tensor[DType, builtins.int, builtins.int, builtins.int], dim: L[2]\n    ) -> L[1]: ...\n    @overload\n    def stride(self) -> Tuple[Unpack[Ts]]: ...\n    @overload\n    def squeeze(\n        self: Tensor[DType, Unpack[Rs], L[1], L[1]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze(\n        self: Tensor[DType, L[1], L[1], Unpack[Rs]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze(\n        self: Tensor[DType, L[1], Unpack[Rs]],\n        dim: L[0] = ...,\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze(\n        self: Tensor[DType, Unpack[Rs], L[1]],\n        dim: L[-1] = ...,\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze(\n        self: Tensor[DType, Unpack[Rs]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    def type_as(\n        self, other: Tensor[DType2, Unpack[Rs]]\n    ) -> Tensor[DType2, Unpack[Rs]]: ...\n    @overload\n    def squeeze_(\n        self: Tensor[DType, Unpack[Rs], L[1], L[1]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze_(\n        self: Tensor[DType, L[1], L[1], Unpack[Rs]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze_(\n        self: Tensor[DType, L[1], Unpack[Rs]],\n        dim: L[0] = ...,\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze_(\n        self: Tensor[DType, Unpack[Rs], L[1]],\n        dim: L[-1] = ...,\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def squeeze_(\n        self: Tensor[DType, Unpack[Rs]], *, out: Optional[Tensor] = ...\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def view(\n        self: Tensor[DType, Unpack[Rs]], *shape: Unpack[Tuple[L[-1], Unpack[Rs2]]]\n    ) -> Tensor[\n        DType, Divide[Product[Unpack[Rs]], Product[Unpack[Rs2]]], Unpack[Rs2]\n    ]: ...\n    @overload\n    def view(\n        self: Tensor[DType, Unpack[Rs]], *shape: Unpack[Tuple[N1, L[-1], Unpack[Rs2]]]\n    ) -> Tensor[\n        DType, N1, Divide[Product[Unpack[Rs]], Product[N1, Unpack[Rs2]]], Unpack[Rs2]\n    ]: ...\n    @overload\n    def view(\n        self: Tensor[DType, Unpack[Rs]], *shape: Unpack[Tuple[Unpack[Rs2], L[-1]]]\n    ) -> Tensor[\n        DType, Unpack[Rs2], Divide[Product[Unpack[Rs]], Product[Unpack[Rs2]]]\n    ]: ...\n    @overload\n    def view(self, *shape: Unpack[Rs]) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def transpose(\n        self: Tensor[DType, Unpack[Rs], N1, N2], dim0: L[-2], dim1: L[-1]\n    ) -> Tensor[DType, Unpack[Rs], N2, N1]: ...\n    @overload\n    def transpose(\n        self: Tensor[DType, Unpack[Rs], N1, N2], dim0: L[-1], dim1: L[-2]\n    ) -> Tensor[DType, Unpack[Rs], N2, N1]: ...\n    @overload\n    def transpose(\n        self: Tensor[DType, N1, N2, Unpack[Rs]], dim0: L[0], dim1: L[1]\n    ) -> Tensor[DType, N2, N1, Unpack[Rs]]: ...\n    @overload\n    def transpose(\n        self: Tensor[DType, N1, N2, Unpack[Rs]], dim0: L[1], dim1: L[0]\n    ) -> Tensor[DType, N2, N1, Unpack[Rs]]: ...\n    @overload\n    def transpose(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]], dim0: L[1], dim1: L[2]\n    ) -> Tensor[DType, N1, N3, N2, Unpack[Rs]]: ...\n    @overload\n    def flatten(\n        self: Tensor[DType, N1, Unpack[Rs], N2],\n        start_dim: L[0] = ...,\n        end_dim: L[-1] = ...,\n    ) -> Tensor[DType, Product[N1, Unpack[Rs], N2]]: ...\n    @overload\n    def flatten(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        start_dim: L[0] = ...,\n        end_dim: L[1] = ...,\n    ) -> Tensor[DType, Multiply[N1, N2], Unpack[Rs]]: ...\n    @overload\n    def flatten(\n        self: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n        start_dim: L[1] = ...,\n        end_dim: L[2] = ...,\n    ) -> Tensor[DType, N1, Multiply[N2, N3], Unpack[Rs]]: ...\n    @overload\n    def flatten(\n        self: Tensor[DType, N1, N2, N3, N4, Unpack[Rs]],\n        start_dim: L[2] = ...,\n        end_dim: L[3] = ...,\n    ) -> Tensor[DType, N1, N2, Multiply[N3, N4], Unpack[Rs]]: ...\n    @overload\n    def flatten(\n        self: Tensor[DType],\n        start_dim: L[0] = ...,\n        end_dim: L[0] = ...,\n    ) -> Tensor[DType, L[1]]: ...\n    @overload\n    def __lt__(\n        self: Tensor[DType, Unpack[Rs]], x: DType\n    ) -> Tensor[bool, Unpack[Rs]]: ...\n    @overload\n    def __lt__(\n        self: Tensor[float32, Unpack[Rs]], x: builtin_float\n    ) -> Tensor[bool, Unpack[Rs]]: ...\n    @overload\n    def __gt__(\n        self: Tensor[DType, Unpack[Rs]], x: DType\n    ) -> Tensor[bool, Unpack[Rs]]: ...\n    @overload\n    def __gt__(\n        self: Tensor[float32, Unpack[Rs]], x: builtin_float\n    ) -> Tensor[bool, Unpack[Rs]]: ...\n    def logical_and(\n        self,\n        other: Tensor[DType2, Unpack[Rs]],\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    def logical_and_(\n        self,\n        other: Tensor[DType2, Unpack[Rs]],\n        *,\n        out: Optional[Tensor] = ...,\n    ) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n    @overload\n    def reshape(\n        self: Tensor[DType, Unpack[Rs]], *shape: Unpack[Tuple[L[-1], Unpack[Rs2]]]\n    ) -> Tensor[\n        DType, Divide[Product[Unpack[Rs]], Product[Unpack[Rs2]]], Unpack[Rs2]\n    ]: ...\n    @overload\n    def reshape(\n        self: Tensor[DType, Unpack[Rs]], *shape: Unpack[Tuple[N1, L[-1], Unpack[Rs2]]]\n    ) -> Tensor[\n        DType, N1, Divide[Product[Unpack[Rs]], Product[N1, Unpack[Rs2]]], Unpack[Rs2]\n    ]: ...\n    @overload\n    def reshape(self, *shape: Unpack[Rs]) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def unbind(\n        self: Tensor[DType, Unpack[Rs], N], dim: L[-1]\n    ) -> Tuple[Tensor[DType, Unpack[Rs]], ...]: ...\n    @overload\n    def unbind(\n        self: Tensor[DType, N, N1, Unpack[Rs]], dim: L[1]\n    ) -> Tuple[Tensor[DType, N, Unpack[Rs]], ...]: ...\n    @overload\n    def unbind(\n        self: Tensor[DType, N, Unpack[Rs]], dim: L[0] = ...\n    ) -> Tuple[Tensor[DType, Unpack[Rs]], ...]: ...\n    def sign(self, *, out: Optional[Tensor] = ...) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def sum(\n        self: Tensor[DType, N1, Unpack[Rs]],\n        dim: L[0],\n        *,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def sum(\n        self: Tensor[DType, N1, N2, Unpack[Rs]],\n        dim: L[1],\n        *,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType, N1, Unpack[Rs]]: ...\n    @overload\n    def sum(\n        self: Tensor[DType, Unpack[Rs], N],\n        dim: L[-1],\n        *,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    @overload\n    def sum(\n        self: Tensor[DType, Unpack[Rs], N1, N2],\n        dim: L[-2],\n        *,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType, Unpack[Rs], N2]: ...\n    @overload\n    def sum(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: L[None] = ...,\n        *,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType]: ...\n    def cumsum(\n        self: Tensor[DType, Unpack[Rs]],\n        dim: builtins.int = ...,\n        dtype: Optional[_device] = ...,\n    ) -> Tensor[DType, Unpack[Rs]]: ...\n    def contiguous(input: Tensor[DType, Unpack[Rs]]) -> Tensor[DType, Unpack[Rs]]: ...\n\nclass LongTensor(Tensor[DType, Unpack[Ts]], Generic[DType, Unpack[Ts]]):\n    @overload\n    def __getitem__(\n        self: LongTensor[DType, Unpack[Rs], N], val: Tuple[object, None]\n    ) -> LongTensor[DType, Unpack[Tuple[Any, ...]]]: ...\n    @overload\n    def __getitem__(\n        self: LongTensor[DType, Unpack[Rs], N], val: Tuple[None, object]\n    ) -> LongTensor[DType, Unpack[Tuple[Any, ...]]]: ...\n    @overload\n    def __getitem__(\n        self: LongTensor[DType, Unpack[Rs], N], val: slice\n    ) -> LongTensor[DType, Unpack[Tuple[Any, ...]]]: ...\n    def __eq__(\n        self: LongTensor[DType, Unpack[Rs]],\n        other: LongTensor[DType, Unpack[Rs]],\n    ) -> LongTensor[bool, Unpack[Rs]]: ...\n\n# NOTE: These `torch` functions below have a method counterpart in `Tensor`. So,\n# if you update the stubs here, please update the method stub as well.\n\ndef allclose(\n    input: Tensor,\n    other: Tensor,\n    rtol: builtin_float = ...,\n    atol: builtin_float = ...,\n    equal_nan: builtins.bool = ...,\n) -> builtins.bool: ...\ndef bitwise_not(\n    input: Tensor[DType, Unpack[Ts]], *, out: Optional[Tensor] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef einsum(\n    equation: str,\n    *operands: Tensor,\n) -> Tensor: ...\n@overload\ndef eye(\n    n: N,\n    *,\n    dtype: Type[float32] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, N, N]: ...\n@overload\ndef eye(\n    n: N,\n    m: M,\n    *,\n    dtype: Type[float32] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, N, M]: ...\n@overload\ndef eye(\n    n: N,\n    *,\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, N, N]: ...\n@overload\ndef eye(\n    n: N,\n    m: M,\n    *,\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, N, M]: ...\n@overload\ndef zeros(\n    size: Tuple[Unpack[Ts]],\n    *,\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef zeros(\n    size: Tuple[Unpack[Ts]],\n    *,\n    dtype: Type[float32] = ...,\n    low: builtins.int = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef zeros(\n    *size: Unpack[Ts],\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef zeros(\n    *size: Unpack[Ts],\n    dtype: Type[float32] = ...,\n    low: builtins.int = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef ones(*size: Unpack[Ts]) -> Tensor[float, Unpack[Ts]]: ...\n@overload\ndef ones(\n    *size: Unpack[Ts], dtype: Type[DType] = ..., device: _device = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef ones_like(\n    input: Tensor[DType, Unpack[Ts]],\n    *,\n    dtype: Type[DType2],\n    memory_format: Optional[memory_format] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType2, Unpack[Ts]]: ...\n@overload\ndef ones_like(\n    input: Tensor[DType, Unpack[Ts]],\n    *,\n    memory_format: Optional[memory_format] = ...,\n    dtype: Type[DType] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef tril(\n    x: Tensor[DType, Unpack[Ts]], diagonal: builtins.int = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef arange(\n    end: N1,\n    *,\n    out: Optional[int] = ...,\n    dtype: Type[int64] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[int64, N1]: ...\n@overload\ndef arange(\n    start: N1,\n    end: N2,\n    *,\n    out: Optional[int] = ...,\n    dtype: Type[int64] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[int64, Add[N2, Multiply[L[-1], N1]]]: ...\n@overload\ndef arange(\n    start: N1,\n    end: N2,\n    step: N3,\n    out: Optional[int] = ...,\n    dtype: Type[int64] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[int64, Divide[Add[N2, Multiply[L[-1], N1]], N3]]: ...\n\n# dtype is explicitly provided.\n@overload\ndef arange(\n    end: N1,\n    *,\n    dtype: Type[DType],\n    out: Optional[int] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, N1]: ...\n@overload\ndef arange(\n    start: N1,\n    end: N2,\n    *,\n    dtype: Type[DType],\n    out: Optional[int] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Add[N2, Multiply[L[-1], N1]]]: ...\n@overload\ndef arange(\n    start: N1,\n    end: N2,\n    step: N3,\n    dtype: Type[DType],\n    out: Optional[int] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Divide[Add[N2, Multiply[L[-1], N1]], N3]]: ...\n@overload\ndef arange(\n    end: builtin_float,\n    start: builtin_float = ...,\n    step: builtin_float = ...,\n    out: Optional[int] = ...,\n    dtype: Type[DType] = ...,\n    layout: Type[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, builtins.str]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[True],\n) -> LongTensor[int64, L[1], Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[True],\n) -> LongTensor[int64, N1, L[1], Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, N1, Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[True],\n) -> LongTensor[int64, N1, N2, L[1], Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, N1, N2, Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[True],\n) -> LongTensor[int64, Unpack[Rs], L[1]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, Unpack[Rs]]: ...\n@overload\ndef argmax(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    keepdim: builtins.bool = ...,\n) -> LongTensor[int64]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[True],\n) -> LongTensor[int64, L[1], Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[True],\n) -> LongTensor[int64, N1, L[1], Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, N1, Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[True],\n) -> LongTensor[int64, N1, N2, L[1], Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, N1, N2, Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[True],\n) -> LongTensor[int64, Unpack[Rs], L[1]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[False] = ...,\n) -> LongTensor[int64, Unpack[Rs]]: ...\n@overload\ndef argmin(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    keepdim: builtins.bool = ...,\n) -> LongTensor[int64]: ...\ndef bmm(\n    input: Tensor[DType, B, N, M], mat2: Tensor[DType, B, M, P]\n) -> Tensor[DType, B, N, P]: ...\n@overload\ndef chunk(input: Tensor[DType, Unpack[Ts], N], chunks: L[2], dim: L[-1]) -> Tuple[\n    Tensor[DType, Unpack[Ts], Divide[N, L[2]]],\n    Tensor[DType, Unpack[Ts], Divide[N, L[2]]],\n]: ...\n@overload\ndef chunk(input: Tensor[DType, N, Unpack[Ts]], chunks: L[2], dim: L[0] = ...) -> Tuple[\n    Tensor[DType, Divide[N, L[2]], Unpack[Ts]],\n    Tensor[DType, Divide[N, L[2]], Unpack[Ts]],\n]: ...\ndef diag(\n    input: Tensor[DType, Unpack[Tuple[Any, ...]]],\n    diagonal: builtins.int = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\ndef diagonal(\n    input: Tensor[DType, Unpack[Tuple[Any, ...]]],\n    offset: builtins.int = ...,\n    dim1: builtins.int = ...,\n    dim2: builtins.int = ...,\n) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\ndef diag_embed(\n    input: Tensor[DType, Unpack[Tuple[Any, ...]]],\n    offset: builtins.int = ...,\n    dim1: builtins.int = ...,\n    dim2: builtins.int = ...,\n) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\n@overload\ndef empty_like(\n    input: Tensor[DType, Unpack[Ts]],\n    *,\n    dtype: Type[DType2],\n    memory_format: Optional[memory_format] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType2, Unpack[Ts]]: ...\n@overload\ndef empty_like(\n    input: Tensor[DType, Unpack[Ts]],\n    *,\n    memory_format: Optional[memory_format] = ...,\n    dtype: Type[DType] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef logical_and(\n    input: Tensor[DType, Unpack[Ts]],\n    other: Tensor[DType2, Unpack[Rs]],\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[bool, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]]]: ...\n@overload\ndef log_softmax(\n    input: Tensor[DType, Unpack[Rs]],\n    dtype: Type[DType2],\n    dim: Optional[builtins.int] = ...,\n) -> Tensor[DType2, Unpack[Rs]]: ...\n@overload\ndef log_softmax(\n    input: Tensor[DType, Unpack[Rs]],\n    *,\n    dim: Optional[builtins.int] = ...,\n    dtype: Optional[DType] = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\ndef masked_select(\n    input: Tensor, mask: Tensor, *, out: Optional[Tensor] = ...\n) -> Tensor: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[True],\n) -> MaxNamedTuple[DType, L[1], Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[False] = ...,\n) -> MaxNamedTuple[DType, Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[True],\n) -> MaxNamedTuple[DType, N1, L[1], Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[False] = ...,\n) -> MaxNamedTuple[DType, N1, Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[True],\n) -> MaxNamedTuple[DType, N1, N2, L[1], Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[False] = ...,\n) -> MaxNamedTuple[DType, N1, N2, Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[True],\n) -> MaxNamedTuple[DType, Unpack[Rs], L[1]]: ...\n@overload\ndef max(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[False] = ...,\n) -> MaxNamedTuple[DType, Unpack[Rs]]: ...\n@overload\ndef max(\n    input: Tensor[DType, Unpack[Rs], N1, N2],\n    dim: L[-2],\n    keepdim: L[True],\n) -> MaxNamedTuple[DType, Unpack[Rs], L[1], N2]: ...\n@overload\ndef max(\n    input: Tensor[DType, Unpack[Rs], N1, N2],\n    dim: L[-2],\n    keepdim: L[False] = ...,\n) -> MaxNamedTuple[DType, Unpack[Rs], N2]: ...\n@overload\ndef max(\n    input: Tensor[DType, Unpack[Rs]],\n) -> Tensor[DType]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[True],\n) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[False] = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[True],\n) -> Tensor[DType, N1, L[1], Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[False] = ...,\n) -> Tensor[DType, N1, Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[True],\n) -> Tensor[DType, N1, N2, L[1], Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[False] = ...,\n) -> Tensor[DType, N1, N2, Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[True],\n) -> Tensor[DType, Unpack[Rs], L[1]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[False] = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef mean(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    keepdim: builtins.bool = ...,\n) -> Tensor[DType]: ...\n@overload\ndef meshgrid(tensor1: Tensor[DType, N1]) -> Tuple[Tensor[DType, N1]]: ...\n@overload\ndef meshgrid(\n    tensor1: Tensor[DType, N1],\n    tensor2: Tensor[DType, N2],\n) -> Tuple[Tensor[DType, N1, N2], Tensor[DType, N1, N2]]: ...\n@overload\ndef meshgrid(\n    tensor1: Tensor[DType, N1],\n    tensor2: Tensor[DType, N2],\n    tensor3: Tensor[DType, N3],\n) -> Tuple[\n    Tensor[DType, N1, N2, N3], Tensor[DType, N1, N2, N3], Tensor[DType, N1, N2, N3]\n]: ...\n@overload\ndef meshgrid(*tensors: Tensor) -> Tuple[Tensor, ...]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[True],\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, L[1], Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n    keepdim: L[False] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[True],\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, N1, L[1], Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    keepdim: L[False] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, N1, Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[True],\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, N1, N2, L[1], Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n    keepdim: L[False] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, N1, N2, Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[True],\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, Unpack[Rs], L[1]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n    keepdim: L[False] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef norm(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    keepdim: builtins.bool = ...,\n    *,\n    out: Optional[Tensor] = ...,\n    p: Optional[Number] = ...,\n) -> Tensor[DType]: ...\n@overload\ndef normal(\n    mean: builtins.float,\n    std: builtins.float,\n    size: Tuple[Unpack[Rs]],\n    *,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, Unpack[Rs]]: ...\n@overload\ndef rand(\n    size: Tuple[Unpack[Ts]],\n    *,\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef rand(\n    size: Tuple[Unpack[Ts]],\n    *,\n    dtype: Type[float32] = ...,\n    low: builtins.int = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef rand(\n    *size: Unpack[Ts],\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef rand(\n    *size: Unpack[Ts],\n    dtype: Type[float32] = ...,\n    low: builtins.int = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    generator: Optional[Generator] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef randint(\n    low: builtins.int,\n    high: builtins.int,\n    size: Tuple[Unpack[Ts]],\n    dtype: Type[DType],\n    *,\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef randint(\n    high: builtins.int,\n    size: Tuple[Unpack[Ts]],\n    dtype: Type[DType],\n    *,\n    low: builtins.int = ...,\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef randint(\n    low: builtins.int,\n    high: builtins.int,\n    size: Tuple[Unpack[Ts]],\n    *,\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    dtype: Type[int64] = ...,\n) -> Tensor[int64, Unpack[Ts]]: ...\n@overload\ndef randint(\n    high: builtins.int,\n    size: Tuple[Unpack[Ts]],\n    *,\n    low: builtins.int = ...,\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Optional[_device] = ...,\n    requires_grad: Optional[builtins.bool] = ...,\n    dtype: Type[int64] = ...,\n) -> Tensor[int64, Unpack[Ts]]: ...\ndef rand_like(\n    input: Tensor[Wild, Unpack[Ts]], dtype: Type[DType]\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef nonzero(\n    input: Tensor[DType, Unpack[Ts]], as_tuple: L[False] = ...\n) -> LongTensor[DType, builtins.int, builtins.int]: ...\n@overload\ndef repeat_interleave(\n    input: Tensor[DType, Unpack[Rs], N1], repeats: N, dim: L[-1]\n) -> Tensor[DType, Unpack[Rs], Multiply[N1, N]]: ...\n@overload\ndef repeat_interleave(\n    input: Tensor[DType, N1, Unpack[Rs]], repeats: N, dim: L[0]\n) -> Tensor[DType, Multiply[N1, N], Unpack[Rs]]: ...\n@overload\ndef repeat_interleave(\n    input: Tensor[DType, N1, N2, Unpack[Rs]], repeats: N, dim: L[1]\n) -> Tensor[DType, N1, Multiply[N2, N], Unpack[Rs]]: ...\n@overload\ndef repeat_interleave(\n    input: Tensor[DType, Unpack[Rs]], repeats: N, dim: L[None] = ...\n) -> Tensor[DType, Product[N, Unpack[Rs]]]: ...\n\n# The output shape here depends on the contents of `repeats`, so give up.\n@overload\ndef repeat_interleave(\n    input: Tensor[DType, Unpack[Rs]], repeats: Tensor, dim: builtins.int = ...\n) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\n@overload\ndef stack(\n    tensors: Tuple[Tensor[DType, N, Unpack[Ts]], Tensor[DType, N, Unpack[Ts]]],\n    dim: L[1],\n    *,\n    out: Optional[Tensor[DType, L[2], Unpack[Ts]]] = ...,\n) -> Tensor[DType, N, L[2], Unpack[Ts]]: ...\n@overload\ndef stack(\n    tensors: Tuple[Tensor[DType, Unpack[Ts]], Tensor[DType, Unpack[Ts]]],\n    dim: L[0] = ...,\n    *,\n    out: Optional[Tensor[DType, L[2], Unpack[Ts]]] = ...,\n) -> Tensor[DType, L[2], Unpack[Ts]]: ...\n@overload\ndef stack(\n    tensors: Tuple[\n        Tensor[DType, N, Unpack[Ts]],\n        Tensor[DType, N, Unpack[Ts]],\n        Tensor[DType, N, Unpack[Ts]],\n    ],\n    dim: L[1],\n    *,\n    out: Optional[Tensor[DType, L[3], Unpack[Ts]]] = ...,\n) -> Tensor[DType, N, L[3], Unpack[Ts]]: ...\n@overload\ndef stack(\n    tensors: Tuple[\n        Tensor[DType, Unpack[Ts]],\n        Tensor[DType, Unpack[Ts]],\n        Tensor[DType, Unpack[Ts]],\n    ],\n    dim: L[0] = ...,\n    *,\n    out: Optional[Tensor[DType, L[3], Unpack[Ts]]] = ...,\n) -> Tensor[DType, L[3], Unpack[Ts]]: ...\n@overload\ndef stack(\n    tensors: Tuple[Any, ...],\n    dim: N = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor: ...\ndef cdist(\n    input: Tensor[DType, Unpack[Ts], P, M],\n    other: Tensor[DType, Unpack[Rs], R, M],\n    p: builtin_float = ...,\n    compute_mode: str = ...,\n) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]]], P, R]: ...\ndef clone(\n    input: Tensor[DType, Unpack[Ts]], *, memory_format: Optional[memory_format] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef count_nonzero(\n    input: Tensor[DType, N1, Unpack[Rs]],\n    dim: L[0],\n) -> Tensor[int64, Unpack[Rs]]: ...\n@overload\ndef count_nonzero(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n) -> Tensor[int64, N1, Unpack[Rs]]: ...\n@overload\ndef count_nonzero(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    dim: L[2],\n) -> Tensor[int64, N1, N2, Unpack[Rs]]: ...\n@overload\ndef count_nonzero(\n    input: Tensor[DType, Unpack[Rs], N1],\n    dim: L[-1],\n) -> Tensor[int64, Unpack[Rs]]: ...\n@overload\ndef count_nonzero(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    keepdim: builtins.bool = ...,\n) -> Tensor[int64]: ...\n@overload\ndef sum(\n    input: Tensor[DType, N1, Unpack[Rs]], dim: L[0], *, dtype: Optional[_device] = ...\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef sum(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    dim: L[1],\n    *,\n    dtype: Optional[_device] = ...,\n) -> Tensor[DType, N1, Unpack[Rs]]: ...\n@overload\ndef sum(\n    input: Tensor[DType, Unpack[Rs], N], dim: L[-1], *, dtype: Optional[_device] = ...\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef sum(\n    input: Tensor[DType, Unpack[Rs], N1, N2],\n    dim: L[-2],\n    *,\n    dtype: Optional[_device] = ...,\n) -> Tensor[DType, Unpack[Rs], N2]: ...\n@overload\ndef sum(\n    input: Tensor[DType, Unpack[Rs]],\n    dim: L[None] = ...,\n    *,\n    dtype: Optional[_device] = ...,\n) -> Tensor[DType]: ...\n@overload\ndef sin(\n    input: Tensor[DType, Unpack[Ts]], *, out: Optional[Tensor[DType, Unpack[Ts]]] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef cos(\n    input: Tensor[DType, Unpack[Ts]], *, out: Optional[Tensor[DType, Unpack[Ts]]] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef exp(\n    input: Tensor[DType, Unpack[Ts]], *, out: Optional[Tensor[DType, Unpack[Ts]]] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef matmul(\n    input: Tensor[DType, N1],\n    other: Tensor[DType, N1],\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType]: ...\n@overload\ndef matmul(\n    input: Tensor[DType, Unpack[Rs], N1, N2],\n    other: Tensor[DType, Unpack[Qs], N2, N3],\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType, Unpack[Broadcast[Tuple[Unpack[Rs]], Tuple[Unpack[Qs]]]], N1, N3]: ...\ndef multinomial(\n    input: Tensor[DType, Unpack[Rs], N1],\n    num_samples: N2,\n    replacement: builtins.bool = ...,\n    *,\n    generator: Optional[Generator] = ...,\n) -> Tensor[DType, Unpack[Rs], N2]: ...\n@overload\ndef unbind(\n    input: Tensor[DType, Unpack[Rs], N], dim: L[-1]\n) -> Tuple[Tensor[DType, Unpack[Rs]], ...]: ...\n@overload\ndef unbind(\n    input: Tensor[DType, N, N1, Unpack[Rs]], dim: L[1]\n) -> Tuple[Tensor[DType, N, Unpack[Rs]], ...]: ...\n@overload\ndef unbind(\n    input: Tensor[DType, N, Unpack[Rs]], dim: L[0] = ...\n) -> Tuple[Tensor[DType, Unpack[Rs]], ...]: ...\n@overload\ndef unsqueeze(\n    input: Tensor[DType, Unpack[Ts]], dim: L[-1]\n) -> Tensor[DType, Unpack[Ts], L[1]]: ...\n@overload\ndef unsqueeze(\n    input: Tensor[DType, Unpack[Ts]], dim: L[0]\n) -> Tensor[DType, L[1], Unpack[Ts]]: ...\n@overload\ndef unsqueeze(\n    input: Tensor[DType, N, Unpack[Ts]], dim: L[1]\n) -> Tensor[DType, N, L[1], Unpack[Ts]]: ...\n@overload\ndef unsqueeze(\n    input: Tensor[DType, N1, N2, Unpack[Ts]], dim: L[2]\n) -> Tensor[DType, N1, N2, L[1], Unpack[Ts]]: ...\n@overload\ndef real(input: Tensor[complex64, Unpack[Ts]]) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef real(input: Tensor[complex128, Unpack[Ts]]) -> Tensor[float64, Unpack[Ts]]: ...\ndef zeros_like(\n    input: Tensor[DType, Unpack[Ts]],\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef randn(\n    size: Tuple[Unpack[Ts]],\n    dtype: Type[DType],\n    *,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef randn(\n    *size: Unpack[Ts],\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef randn(\n    size: Tuple[Unpack[Ts]],\n    *,\n    out: Optional[Tensor] = ...,\n    dtype: Type[float32] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef randn(\n    *size: Unpack[Ts],\n    out: Optional[Tensor] = ...,\n    dtype: Type[float32] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef all(\n    input: Tensor[DType, Unpack[Ts]],\n) -> Tensor[bool, L[1]]: ...\n@overload\ndef all(\n    input: Tensor[DType, N, Unpack[Ts]],\n    dim: L[0],\n) -> Tensor[bool, Unpack[Ts]]: ...\n@overload\ndef all(\n    input: Tensor[DType, N1, N2, Unpack[Ts]],\n    dim: L[1],\n) -> Tensor[bool, N1, Unpack[Ts]]: ...\n@overload\ndef randperm(\n    n: N,\n    *,\n    dtype: Type[DType],\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, N]: ...\n@overload\ndef randperm(\n    n: N,\n    *,\n    generator: Optional[Generator] = ...,\n    out: Optional[Tensor] = ...,\n    dtype: Type[float32] = ...,\n    layout: Optional[layout] = ...,\n    device: Union[_device, str, None] = ...,\n    pin_memory: builtins.bool = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[float32, N]: ...\ndef sqrt(\n    input: Tensor[DType, Unpack[Ts]], *, out: Optional[Tensor[DType, Unpack[Ts]]] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef where(\n    condition: Tensor[bool, Unpack[Ts]],\n    x: Tensor[DType, Unpack[Rs]],\n    y: Tensor[DType, Unpack[Rs2]],\n) -> Tensor[\n    DType,\n    Unpack[\n        Broadcast[Broadcast[Tuple[Unpack[Ts]], Tuple[Unpack[Rs]]], Tuple[Unpack[Rs2]]]\n    ],\n]: ...\n\n# The exact output shape in this case depends on the contents of the tensor,\n# meaning this is too dynamic for shape types.\n@overload\ndef where(condition: Tensor[DType, Unpack[Ts]]) -> Any: ...\n@overload\ndef diff(\n    input: Tensor[DType, Unpack[Rs], Add[N1, L[1]], N2],\n    dim: L[-2],\n) -> Tensor[DType, Unpack[Rs], N1, N2]: ...\n@overload\ndef diff(\n    input: Tensor[DType, Add[N, L[1]], Unpack[Rs]],\n    dim: L[0],\n) -> Tensor[DType, N, Unpack[Rs]]: ...\n@overload\ndef diff(\n    input: Tensor[DType, N1, Add[N2, L[1]], Unpack[Rs]],\n    dim: L[1],\n) -> Tensor[DType, N1, N2, Unpack[Rs]]: ...\n@overload\ndef diff(\n    input: Tensor[DType, Unpack[Rs], Add[N, L[1]]],\n    dim: L[-1] = ...,\n) -> Tensor[DType, Unpack[Rs], N]: ...\ndef argsort(\n    input: Tensor[DType, Unpack[Ts]],\n    dim: builtins.int = ...,\n    descending: builtin_bool = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n\n# Input tuple has 2 elements.\n@overload\ndef cat(\n    tensors: Tuple[Tensor[DType, Unpack[Rs], N1], Tensor[DType, Unpack[Rs], N2]],\n    dim: L[-1],\n    *,\n    out: Any = ...,\n) -> Tensor[DType, Unpack[Rs], Add[N1, N2]]: ...\n@overload\ndef cat(\n    tensors: Tuple[Tensor[DType, N, N1, Unpack[Rs]], Tensor[DType, N, N2, Unpack[Rs]]],\n    dim: L[1],\n    *,\n    out: Any = ...,\n) -> Tensor[DType, N, Add[N1, N2], Unpack[Rs]]: ...\n@overload\ndef cat(\n    tensors: Tuple[Tensor[DType, N1, Unpack[Rs]], Tensor[DType, N2, Unpack[Rs]]],\n    dim: L[0] = ...,\n    *,\n    out: Any = ...,\n) -> Tensor[DType, Add[N1, N2], Unpack[Rs]]: ...\n\n# Input tuple has 3 elements.\n@overload\ndef cat(\n    tensors: Tuple[\n        Tensor[DType, Unpack[Rs], N1],\n        Tensor[DType, Unpack[Rs], N2],\n        Tensor[DType, Unpack[Rs], N3],\n    ],\n    dim: L[-1],\n    *,\n    out: Any = ...,\n) -> Tensor[DType, Unpack[Rs], Add[Add[N1, N2], N3]]: ...\n@overload\ndef cat(\n    tensors: Tuple[\n        Tensor[DType, N, N1, Unpack[Rs]],\n        Tensor[DType, N, N2, Unpack[Rs]],\n        Tensor[DType, N, N3, Unpack[Rs]],\n    ],\n    dim: L[1],\n    *,\n    out: Any = ...,\n) -> Tensor[DType, N, Add[Add[N1, N2], N3], Unpack[Rs]]: ...\n@overload\ndef cat(\n    tensors: Tuple[\n        Tensor[DType, N1, Unpack[Rs]],\n        Tensor[DType, N2, Unpack[Rs]],\n        Tensor[DType, N3, Unpack[Rs]],\n    ],\n    dim: L[0] = ...,\n    *,\n    out: Any = ...,\n) -> Tensor[DType, Add[Add[N1, N2], N3], Unpack[Rs]]: ...\n\n# This takes an arbitrary-length list of tensors and concatenates them across an\n# axis. We don't know the length of the list and thus can't tell the final\n# dimensions of the tensor.\n@overload\ndef cat(\n    tensors: Iterable[Tensor[DType, Unpack[Ts]]],\n    dim: builtins.int = ...,\n    *,\n    out: Any = ...,\n) -> Tensor[DType, Unpack[Tuple[Any, ...]]]: ...\n\nsave: Any\nmanual_seed: Any\nload: Any\nfrom_numpy: Any\nno_grad: Any\n\ndef sign(\n    input: Tensor[DType, Unpack[Rs]], *, out: Optional[Tensor] = ...\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef sparse_coo_tensor(\n    indices: Tensor,\n    values: Union[Tensor, List[Any]],\n    size: Tuple[Unpack[Rs]],\n    *,\n    dtype: Optional[DType],\n    device: Union[_device, str, None] = ...,\n    requires_grad: builtin_bool = ...,\n) -> Tensor[DType, Unpack[Rs]]: ...\n@overload\ndef sparse_coo_tensor(\n    indices: Tensor,\n    values: Union[Tensor, List[Any]],\n    size: Tuple[Unpack[Rs]],\n    *,\n    dtype: Type[float32] = ...,\n    device: Union[_device, str, None] = ...,\n    requires_grad: builtin_bool = ...,\n) -> Tensor[float32, Unpack[Rs]]: ...\n@overload\ndef sparse_coo_tensor(\n    indices: Tensor,\n    values: Union[Tensor, List[Any]],\n    size: L[None] = ...,\n    *,\n    dtype: Type[Any] = ...,\n    device: Union[_device, str, None] = ...,\n    requires_grad: builtin_bool = ...,\n) -> Tensor: ...\n@overload\ndef softmax(\n    input: Tensor[DType, Unpack[Ts]], dim: builtins.int\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef softmax(\n    input: Tensor[DType, Unpack[Ts]], dim: builtins.int, dtype: Type[DType2] = ...\n) -> Tensor[DType2, Unpack[Ts]]: ...\n@overload\ndef transpose(\n    input: Tensor[DType, Unpack[Rs], N1, N2], dim0: L[-2], dim1: L[-1]\n) -> Tensor[DType, Unpack[Rs], N2, N1]: ...\n@overload\ndef transpose(\n    input: Tensor[DType, Unpack[Rs], N1, N2], dim0: L[-1], dim1: L[-2]\n) -> Tensor[DType, Unpack[Rs], N2, N1]: ...\n@overload\ndef transpose(\n    input: Tensor[DType, N1, N2, Unpack[Rs]], dim0: L[0], dim1: L[1]\n) -> Tensor[DType, N2, N1, Unpack[Rs]]: ...\n@overload\ndef transpose(\n    input: Tensor[DType, N1, N2, Unpack[Rs]], dim0: L[1], dim1: L[0]\n) -> Tensor[DType, N2, N1, Unpack[Rs]]: ...\n@overload\ndef transpose(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]], dim0: L[1], dim1: L[2]\n) -> Tensor[DType, N1, N3, N2, Unpack[Rs]]: ...\n@overload\ndef empty(\n    size: Tuple[Unpack[Ts]],\n    dtype: Type[DType],\n    *,\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef empty(\n    *size: Unpack[Ts],\n    dtype: Type[DType],\n    out: Optional[Tensor] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n    pin_memory: builtins.bool = ...,\n    memory_format: Optional[memory_format] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n@overload\ndef empty(\n    size: Tuple[Unpack[Ts]],\n    *,\n    out: Optional[Tensor] = ...,\n    dtype: Type[float32] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n    pin_memory: builtins.bool = ...,\n    memory_format: Optional[memory_format] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef empty(\n    *size: Unpack[Ts],\n    out: Optional[Tensor] = ...,\n    dtype: Type[float32] = ...,\n    layout: Optional[layout] = ...,\n    device: _device = ...,\n    requires_grad: builtins.bool = ...,\n    pin_memory: builtins.bool = ...,\n    memory_format: Optional[memory_format] = ...,\n) -> Tensor[float32, Unpack[Ts]]: ...\n@overload\ndef flatten(\n    input: Tensor[DType, N1, Unpack[Rs], N2],\n    start_dim: L[0] = ...,\n    end_dim: L[-1] = ...,\n) -> Tensor[DType, Product[N1, Unpack[Rs], N2]]: ...\n@overload\ndef flatten(\n    input: Tensor[DType, N1, N2, Unpack[Rs]],\n    start_dim: L[0] = ...,\n    end_dim: L[1] = ...,\n) -> Tensor[DType, Multiply[N1, N2], Unpack[Rs]]: ...\n@overload\ndef flatten(\n    input: Tensor[DType, N1, N2, N3, Unpack[Rs]],\n    start_dim: L[1] = ...,\n    end_dim: L[2] = ...,\n) -> Tensor[DType, N1, Multiply[N2, N3], Unpack[Rs]]: ...\n@overload\ndef flatten(\n    input: Tensor[DType, N1, N2, N3, N4, Unpack[Rs]],\n    start_dim: L[2] = ...,\n    end_dim: L[3] = ...,\n) -> Tensor[DType, N1, N2, Multiply[N3, N4], Unpack[Rs]]: ...\n@overload\ndef flatten(\n    input: Tensor[DType],\n    start_dim: L[0] = ...,\n    end_dim: L[0] = ...,\n) -> Tensor[DType, L[1]]: ...\n@overload\ndef reshape(\n    input: Tensor[DType, Unpack[Rs]], shape: Tuple[L[-1], Unpack[Rs2]]\n) -> Tensor[DType, Divide[Product[Unpack[Rs]], Product[Unpack[Rs2]]], Unpack[Rs2]]: ...\n@overload\ndef reshape(\n    input: Tensor[DType, Unpack[Rs]], shape: Tuple[N1, L[-1], Unpack[Rs2]]\n) -> Tensor[\n    DType, N1, Divide[Product[Unpack[Rs]], Product[N1, Unpack[Rs2]]], Unpack[Rs2]\n]: ...\n@overload\ndef reshape(\n    input: Tensor[DType, Unpack[Rs]], shape: Tuple[Unpack[Rs2]]\n) -> Tensor[DType, Unpack[Rs2]]: ...\n"
  },
  {
    "path": "stubs/torch/autograd/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, Optional, Sequence, Union\n\nimport torch\n\n_TensorOrTensors = Union[torch.Tensor, Sequence[torch.Tensor]]\n\nclass Function:\n    @classmethod\n    def apply(cls, *args: object) -> Any: ...\n\nclass enable_grad:\n    def __enter__(self) -> None: ...\n    def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None: ...\n\ndef backward(\n    tensors: _TensorOrTensors,\n    grad_tensors: Optional[_TensorOrTensors] = None,\n    retain_graph: Optional[bool] = None,\n    create_graph: bool = False,\n    grad_variables: Optional[_TensorOrTensors] = None,\n    inputs: Optional[_TensorOrTensors] = None,\n) -> None: ...\n"
  },
  {
    "path": "stubs/torch/autograd/profiler.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport contextlib\nfrom typing import Any\n\nclass record_function(contextlib.ContextDecorator):\n    def __init__(self, name: str) -> None: ...\n    def __enter__(self) -> Any: ...\n    def __exit__(self, *exctype: Any) -> None: ...\n"
  },
  {
    "path": "stubs/torch/cuda/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/fft/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, overload, Tuple, TypeVar\n\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom torch import complex64, Tensor\n\nDType = TypeVar(\"DType\")\n\nTs = TypeVarTuple(\"Ts\")\n\n@overload\ndef fft(\n    input: Tensor[DType, Unpack[Ts]],\n    n: int = ...,\n    dim: int = ...,\n    norm: str = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[complex64, Unpack[Ts]]: ...\n@overload\ndef fft2(\n    input: Tensor[DType, Unpack[Ts]],\n    s: Optional[Tuple[int, ...]] = ...,\n    dim: Tuple[int, ...] = ...,\n    norm: Optional[str] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[DType, Unpack[Ts]]: ...\n"
  },
  {
    "path": "stubs/torch/hub.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndownload_url_to_file = Any\n_download_url_to_file = Any\n"
  },
  {
    "path": "stubs/torch/linalg/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, overload, Tuple, TypeVar\n\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom torch import complex128, complex64, float32, float64, Tensor\nfrom typing_extensions import Literal as L\n\nDType = TypeVar(\"DType\")\nFloatOrDouble = TypeVar(\"FloatOrDouble\", float32, float64, complex64, complex128)\nM = TypeVar(\"M\", bound=int)\nN = TypeVar(\"N\", bound=int)\n\nTs = TypeVarTuple(\"Ts\")\n\n@overload\ndef pinv(\n    input: Tensor[FloatOrDouble, Unpack[Ts], N1, N1],\n    rcond: float = ...,\n    *,\n    hermitian: L[True],\n    out: Optional[Tensor] = ...,\n) -> Tensor[FloatOrDouble, Unpack[Ts], N1, N1]: ...\n@overload\ndef pinv(\n    input: Tensor[FloatOrDouble, Unpack[Ts], N1, N2],\n    rcond: float = ...,\n    hermitian: bool = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tensor[FloatOrDouble, Unpack[Ts], N2, N1]: ...\n@overload\ndef qr(\n    A: Tensor[FloatOrDouble, Unpack[Ts], M, N],\n    mode: L[\"complete\"],\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tuple[\n    Tensor[FloatOrDouble, Unpack[Ts], M, M], Tensor[FloatOrDouble, Unpack[Ts], M, N]\n]: ...\n\n# The return type should use k=min(m, n), but we don't have that operator yet.\n# Assume that M <= N for now.\n@overload\ndef qr(\n    A: Tensor[FloatOrDouble, Unpack[Ts], M, N],\n    mode: L[\"reduced\"] = ...,\n    *,\n    out: Optional[Tensor] = ...,\n) -> Tuple[\n    Tensor[FloatOrDouble, Unpack[Ts], M, M], Tensor[FloatOrDouble, Unpack[Ts], M, N]\n]: ...\n@overload\ndef norm(\n    A: Tensor[DType, Unpack[Ts], N, M], dim: Tuple[L[-2], L[-1]]\n) -> Tensor[DType, Unpack[Ts]]: ...\n"
  },
  {
    "path": "stubs/torch/nn/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport builtins\nfrom typing import (\n    Any,\n    Generic,\n    Iterable,\n    Iterator,\n    List,\n    Optional,\n    overload,\n    Tuple,\n    Type,\n    TypeVar,\n    Union,\n)\n\nimport torch\nfrom pyre_extensions import Add, Divide, Multiply, Subtract, TypeVarTuple, Unpack\nfrom torch import Tensor\nfrom typing_extensions import Literal as L\n\nDType = TypeVar(\"DType\")\nT = TypeVar(\"T\")\nTs = TypeVarTuple(\"Ts\")\nInputSize = TypeVar(\"InputSize\", bound=int)\nOutputSize = TypeVar(\"OutputSize\", bound=int)\nHiddenSize = TypeVar(\"HiddenSize\", bound=int)\nBatch = TypeVar(\"Batch\", bound=int)\nN = TypeVar(\"N\", bound=int)\nEmbeddingDimension = TypeVar(\"EmbeddingDimension\", bound=int)\nH = TypeVar(\"H\", bound=int)\nW = TypeVar(\"W\", bound=int)\n\nclass Module:\n    def __call__(self, *args: Any, **kwargs: Any) -> Any: ...\n    def parameters(self) -> Iterator[Any]: ...\n    def double(self: T) -> T: ...\n    def to(self, dtype: Type[T], device: torch._device = ...) -> Module: ...\n    def eval(self) -> Module: ...\n    def train(self, mode: bool) -> Module: ...\n    def register_parameter(self, name: str, param: Optional[Parameter]) -> None: ...\n    bias: Parameter = ...\n    training: bool = ...\n\nclass LSTMCell(Module, Generic[InputSize, HiddenSize]):\n    def __init__(\n        self, input_size: InputSize, hidden_size: HiddenSize, bias: bool = ...\n    ) -> None: ...\n    def __call__(\n        self,\n        input: Tensor[DType, Batch, InputSize],\n        hidden: Tuple[\n            Tensor[DType, Batch, HiddenSize], Tensor[DType, Batch, HiddenSize]\n        ] = ...,\n    ) -> Tuple[Tensor[DType, Batch, HiddenSize], Tensor[DType, Batch, HiddenSize]]: ...\n\nclass Linear(Module, Generic[InputSize, OutputSize]):\n    def __init__(\n        self, in_features: InputSize, out_features: OutputSize, bias: bool = ...\n    ) -> None: ...\n    def __call__(\n        self,\n        input: Tensor[DType, N, Unpack[Ts], InputSize],\n    ) -> Tensor[DType, N, Unpack[Ts], OutputSize]: ...\n\nclass _Loss(Module): ...\n\nclass MSELoss(_Loss):\n    def __init__(\n        self,\n        size_average: Optional[bool] = ...,\n        reduce: Optional[bool] = ...,\n        reduction: str = ...,\n    ) -> None: ...\n    def __call__(\n        self,\n        input: Tensor[DType, N, Unpack[Ts]],\n        target: Tensor[DType, N, Unpack[Ts]],\n    ) -> Tensor[DType]: ...\n\nInChannels = TypeVar(\"InChannels\", bound=int)\nOutChannels = TypeVar(\"OutChannels\", bound=int)\nKernelSize1 = TypeVar(\"KernelSize1\", bound=int)\nKernelSize2 = TypeVar(\"KernelSize2\", bound=int)\nStride = TypeVar(\"Stride\", bound=int)\nBatch = TypeVar(\"Batch\", bound=int)\nHeight = TypeVar(\"Height\", bound=int)\nWidth = TypeVar(\"Width\", bound=int)\nChannels = TypeVar(\"Channels\", bound=int)\nPadding = TypeVar(\"Padding\", bound=int)\nPadding1 = TypeVar(\"Padding1\", bound=int)\nPadding2 = TypeVar(\"Padding2\", bound=int)\n\nclass Conv2d(\n    Module,\n    Generic[InChannels, OutChannels, KernelSize1, KernelSize2, Padding1, Padding2],\n):\n    def __init__(\n        self,\n        in_channels: InChannels,\n        out_channels: OutChannels,\n        kernel_size: Tuple[KernelSize1, KernelSize2],\n        padding: Tuple[Padding1, Padding2],\n        bias: bool = ...,\n    ) -> None: ...\n    def __call__(\n        self, input: Tensor[DType, Batch, InChannels, Height, Width]\n    ) -> Tensor[\n        DType,\n        Batch,\n        OutChannels,\n        # We assume stride = 1.\n        # (H − K[0] + 2P[0]) + 1.\n        Add[Add[Subtract[Height, KernelSize1], Multiply[Padding1, L[2]]], L[1]],\n        # (W − K[1] + 2P[1]) + 1.\n        Add[Add[Subtract[Width, KernelSize2], Multiply[Padding2, L[2]]], L[1]],\n    ]: ...\n\nclass ReflectionPad2d(Module, Generic[Padding]):\n    def __init__(\n        self,\n        padding: Padding,\n    ) -> None: ...\n    def __call__(\n        self,\n        input: Tensor[DType, Batch, Channels, Height, Width],\n    ) -> Tensor[\n        DType,\n        Batch,\n        Channels,\n        Add[Add[Height, Padding], Padding],\n        Add[Add[Width, Padding], Padding],\n    ]: ...\n\nclass InstanceNorm2d(Generic[Channels]):\n    def __init__(self, num_features: Channels, affine: bool = False) -> None: ...\n    def __call__(\n        self, input: Tensor[DType, Batch, Channels, Height, Width]\n    ) -> Tensor[DType, Batch, Channels, Height, Width]: ...\n\nclass LeakyReLU(Module):\n    def __init__(self, negative_slope: float = ..., inplace: bool = ...) -> None: ...\n    def __call__(\n        self, input: Tensor[DType, N, Unpack[Ts]]\n    ) -> Tensor[DType, N, Unpack[Ts]]: ...\n\nclass ReLU(Module):\n    def __call__(\n        self, input: Tensor[DType, Batch, Channels, Height, Width]\n    ) -> Tensor[DType, Batch, Channels, Height, Width]: ...\n\nclass GELU(Module):\n    def __call__(\n        self, input: Tensor[DType, Batch, Channels, Height, Width]\n    ) -> Tensor[DType, Batch, Channels, Height, Width]: ...\n\nclass Dropout(Module):\n    def __init__(self, p: float, inplace: bool = ...) -> None: ...\n    def __call__(\n        self, input: Tensor[DType, Unpack[Ts]]\n    ) -> Tensor[DType, Unpack[Ts]]: ...\n\nclass Embedding(Module, Generic[N, EmbeddingDimension]):\n    def __init__(\n        self,\n        num_embeddings: N,\n        embedding_dim: EmbeddingDimension,\n        padding_idx: Optional[int] = ...,\n        max_norm: Optional[float] = ...,\n        norm_type: float = ...,\n        scale_grad_by_freq: bool = ...,\n        sparse: bool = ...,\n        _weight: Optional[Tensor] = ...,\n    ) -> None: ...\n    @property\n    def padding_idx(self) -> int: ...\n    @property\n    def max_norm(self) -> float: ...\n    @property\n    def norm_type(self) -> float: ...\n    @property\n    def scale_grad_by_freq(self) -> bool: ...\n    @property\n    def sparse(self) -> bool: ...\n    @property\n    def weight(self) -> Tensor[torch.float32, N, EmbeddingDimension]: ...\n    @classmethod\n    def from_pretrained(\n        cls,\n        embeddings: Tensor[DType, N, EmbeddingDimension],\n        freeze: bool = True,\n        padding_idx: Optional[int] = None,\n        max_norm: Optional[float] = None,\n        norm_type: float = 2.0,\n        scale_grad_by_freq: bool = False,\n        sparse: bool = False,\n    ) -> Embedding[N, EmbeddingDimension]: ...\n    def forward(\n        self, x: Tensor[DType, Unpack[Ts]]\n    ) -> Tensor[DType, Unpack[Ts], EmbeddingDimension]: ...\n    def __call__(\n        self, x: Tensor[DType, Unpack[Ts]]\n    ) -> Tensor[DType, Unpack[Ts], EmbeddingDimension]: ...\n\n_shape_t = Union[int, List[int], Tuple[Any, ...]]\n\nclass LayerNorm(Module):\n    def __init__(\n        self,\n        normalized_shape: _shape_t,\n        eps: float = ...,\n        elementwise_affine: bool = ...,\n        device=...,\n        dtype=...,\n    ) -> None: ...\n    def forward(self, x: Tensor[DType, Unpack[Ts]]) -> Tensor[DType, Unpack[Ts]]: ...\n    def __call__(self, x: Tensor[DType, Unpack[Ts]]) -> Tensor[DType, Unpack[Ts]]: ...\n\nclass AdaptiveAvgPool2d(Module, Generic[H, W]):\n    @overload\n    def __new__(\n        self,\n        output_size: Tuple[N, L[None]],\n    ) -> AdaptiveAvgPool2d[N, L[-1]]: ...\n    @overload\n    def __new__(\n        self,\n        output_size: Tuple[L[None], N],\n    ) -> AdaptiveAvgPool2d[L[-1], N]: ...\n    @overload\n    def __new__(\n        self,\n        output_size: H,\n    ) -> AdaptiveAvgPool2d[H, H]: ...\n    @overload\n    def __new__(\n        self,\n        output_size: Tuple[H, W],\n    ) -> AdaptiveAvgPool2d[H, W]: ...\n    def forward(self, x: Tensor[DType, Unpack[Ts]]) -> Tensor[DType, Unpack[Ts]]: ...\n    @overload\n    def __call__(\n        self: AdaptiveAvgPool2d[L[-1], W], x: Tensor[DType, Unpack[Ts], N, int]\n    ) -> Tensor[DType, Unpack[Ts], N, W]: ...\n    @overload\n    def __call__(\n        self: AdaptiveAvgPool2d[H, L[-1]], x: Tensor[DType, Unpack[Ts], int, N]\n    ) -> Tensor[DType, Unpack[Ts], H, N]: ...\n    @overload\n    def __call__(\n        self: AdaptiveAvgPool2d[H, W], x: Tensor[DType, Unpack[Ts], int, int]\n    ) -> Tensor[DType, Unpack[Ts], H, W]: ...\n\nclass ModuleList(Module):\n    def __init__(self, modules: Optional[Iterable[Module]] = ...) -> None: ...\n    def __iter__(self) -> Iterator[Module]: ...\n    def __len__(self) -> int: ...\n\nclass Parameter(Tensor[DType, Unpack[Ts]]):\n    def __init__(\n        self, data: Tensor[DType, Unpack[Ts]] = ..., requires_grad: builtins.bool = ...\n    ) -> None: ...\n\nSequential: Any = ...\n"
  },
  {
    "path": "stubs/torch/nn/functional/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import overload, Tuple, TypeVar\n\nimport torch\n\n# pyre-ignore[21]: Could not find module `pyre_extensions`. (Spurious error)\nfrom pyre_extensions import Add, TypeVarTuple, Unpack\nfrom torch import Tensor\nfrom typing_extensions import Literal as L\n\nDType = TypeVar(\"DType\")\nT = TypeVar(\"T\")\nTs = TypeVarTuple(\"Ts\")\nN = TypeVar(\"N\", bound=int)\nM = TypeVar(\"M\", bound=int)\nN1 = TypeVar(\"N1\", bound=int)\nN2 = TypeVar(\"N2\", bound=int)\nN3 = TypeVar(\"N3\", bound=int)\nN4 = TypeVar(\"N4\", bound=int)\n\n@overload\ndef pad(\n    input: Tensor[DType, Unpack[Ts], N],\n    pad: Tuple[N1, N2],\n    mode: str = ...,\n    value: float = ...,\n) -> Tensor[DType, Unpack[Ts], Add[Add[N, N1], N2]]: ...\n@overload\ndef pad(\n    input: Tensor[DType, Unpack[Ts], N, M],\n    pad: Tuple[N1, N2, N3, N4],\n    mode: str = ...,\n    value: float = ...,\n) -> Tensor[DType, Unpack[Ts], Add[Add[N, N3], N4], Add[Add[M, N1], N2]]: ...\n\nx: str\n"
  },
  {
    "path": "stubs/torch/nn/functional.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import overload, Tuple, TypeVar\n\nimport torch\n\n# pyre-ignore[21]: Could not find module `pyre_extensions`. (Spurious error)\nfrom pyre_extensions import Add, TypeVarTuple, Unpack\nfrom torch import Tensor\nfrom typing_extensions import Literal as L\n\nDType = TypeVar(\"DType\")\nT = TypeVar(\"T\")\nTs = TypeVarTuple(\"Ts\")\nN = TypeVar(\"N\", bound=int)\nM = TypeVar(\"M\", bound=int)\nN1 = TypeVar(\"N1\", bound=int)\nN2 = TypeVar(\"N2\", bound=int)\nN3 = TypeVar(\"N3\", bound=int)\nN4 = TypeVar(\"N4\", bound=int)\n\n@overload\ndef pad(\n    input: Tensor[DType, Unpack[Ts], N],\n    pad: Tuple[N1, N2],\n    mode: str = ...,\n    value: float = ...,\n) -> Tensor[DType, Unpack[Ts], Add[Add[N, N1], N2]]: ...\n@overload\ndef pad(\n    input: Tensor[DType, Unpack[Ts], N, M],\n    pad: Tuple[N1, N2, N3, N4],\n    mode: str = ...,\n    value: float = ...,\n) -> Tensor[DType, Unpack[Ts], Add[Add[N, N3], N4], Add[Add[M, N1], N2]]: ...\n\nx: str\n"
  },
  {
    "path": "stubs/torch/nn/init.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Tuple, TypeVar\n\nimport torch\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom torch import Tensor\n\nTs = TypeVarTuple(\"Ts\")\nDType = TypeVar(\"DType\")\n\ndef _calculate_fan_in_and_fan_out(tensor: Tensor) -> Tuple[int, int]: ...\ndef constant_(\n    tensor: Tensor[DType, Unpack[Ts]], val: float\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef kaiming_uniform_(\n    tensor: Tensor[DType, Unpack[Ts]], a=0, mode=\"fan_in\", nonlinearity=\"leaky_relu\"\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef normal_(\n    tensor: Tensor[DType, Unpack[Ts]], mean: float = ..., std: float = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef uniform_(\n    tensor: Tensor[DType, Unpack[Ts]], a: float = ..., b: float = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\ndef _no_grad_uniform_(tensor: Tensor, a, b): ...\ndef xavier_uniform_(tensor: Tensor, gain: float = ...) -> Tensor: ...\n"
  },
  {
    "path": "stubs/torch/nn/utils/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Iterable, TypeVar, Union\n\nfrom pyre_extensions import TypeVarTuple\nfrom torch import Tensor\n\nDType = TypeVar(\"DType\")\n\nTs = TypeVarTuple(\"Ts\")\n_tensor_or_tensors = Union[Tensor, Iterable[Tensor]]\n\ndef clip_grad_norm_(\n    parameters: _tensor_or_tensors,\n    max_norm: float,\n    norm_type: float = ...,\n    error_if_nonfinite: bool = ...,\n) -> Tensor: ...\ndef clip_grad_value_(\n    parameters: _tensor_or_tensors,\n    clip_value: float,\n) -> Tensor: ...\n"
  },
  {
    "path": "stubs/torch/onnx.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "stubs/torch/ops.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/optim/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/profiler/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/random/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef initial_seed() -> int: ...\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/torch/sparse/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, overload, TypeVar\n\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom torch import Tensor\nfrom typing_extensions import Literal as L\n\nfrom . import nn as nn\n\nDType = TypeVar(\"DType\")\nDType2 = TypeVar(\"DType2\")\n\nTs = TypeVarTuple(\"Ts\")\n\n@overload\ndef softmax(\n    input: Tensor[DType, Unpack[Ts]], dim: int, dtype: Optional[DType2]\n) -> Tensor[DType2, Unpack[Ts]]: ...\n@overload\ndef softmax(\n    input: Tensor[DType, Unpack[Ts]], dim: int, dtype: Optional[DType] = ...\n) -> Tensor[DType, Unpack[Ts]]: ...\n"
  },
  {
    "path": "stubs/torch/utils/data.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\nDataLoader = Any\n"
  },
  {
    "path": "stubs/torch/utils/model_zoo.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\n_download_url_to_file = Any\n"
  },
  {
    "path": "stubs/torch_stub_tests.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, Tuple, TypeVar\n\nimport torch\nimport torch.nn as nn\nfrom pyre_extensions import TypeVarTuple, Unpack\nfrom torch import Tensor\nfrom typing_extensions import Literal as L\n\nTs = TypeVarTuple(\"Ts\")\nN = TypeVar(\"N\", bound=int)\n\n# flake8: noqa\n\n\"\"\"\nTensor shape signatures can get complicated and hard to debug. We are basically\nwriting code at the level of types.\n\nIt's helpful to have type-level unit tests for the stubs.\n\nTake care to add both a positive and a negative test for your stub. That way,\neven if someone changes the stub to return a bad type like `Any`, we will still\nbe warned by an unused-ignore error. Otherwise, `y: Tensor[int, L[2], L[3]] =\nfoo(x)` would silently pass because `Any` is compatible with any type.\n\nUse `pyre --output=json | pyre-upgrade` to add the `pyre-fixme` comment for you.\n\"\"\"\n\n\ndef test_sin() -> None:\n    x: Tensor[int, L[2], L[3]]\n    same_shape_as_x: Tensor[int, L[2], L[3]]\n    not_same_shape_as_x: Tensor[int, L[2], L[99]]\n    y: Tensor[int, L[2], L[3]] = torch.sin(x)\n    # pyre-fixme[9]: y2 has type `Tensor[int, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[4]]`; used as `Tensor[int,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[3]]`.\n    y2: Tensor[int, L[2], L[4]] = torch.sin(x)\n\n    y3: Tensor[int, L[2], L[3]] = torch.sin(x, out=same_shape_as_x)\n    # pyre-fixme[6]: Expected `Tensor[Variable[torch.DType], *torch.Ts]` for 2nd\n    #  param but got `Tensor[int, int, int]`.\n    # pyre-fixme[9]: y4 has type `Tensor[int, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[4]]`; used as `Tensor[int,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[3]]`.\n    y4: Tensor[int, L[2], L[4]] = torch.sin(x, out=not_same_shape_as_x)\n    y5: Tensor[int, L[2], L[3]] = torch.sin(x, out=None)\n\n\ndef test_unsqueeze() -> None:\n    x: Tensor[int, L[2], L[3]]\n    y: Tensor[int, L[1], L[2], L[3]] = x.unsqueeze(0)\n    y_torch_function: Tensor[int, L[1], L[2], L[3]] = torch.unsqueeze(x, 0)\n    y2: Tensor[int, L[2], L[1], L[3]] = x.unsqueeze(1)\n    y3: Tensor[int, L[2], L[3], L[1]] = x.unsqueeze(-1)\n    # pyre-fixme[9]: y4 has type `Tensor[int, typing_extensions.Literal[99]]`; used\n    #  as `Tensor[int, typing_extensions.Literal[1], typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y4: Tensor[int, L[99]] = x.unsqueeze(0)\n\n    empty: Tensor[int]\n    y5: Tensor[int, L[1]] = empty.unsqueeze(0)\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 1st param but got\n    #  `typing_extensions.Literal[1]`.\n    y6: Tensor[int, L[1]] = empty.unsqueeze(1)\n    y7: Tensor[int, L[2], L[3], L[1]] = x.unsqueeze(2)\n\n\ndef test_unsqueeze_() -> None:\n    x: Tensor[int, L[2], L[3]]\n    y: Tensor[int, L[1], L[2], L[3]] = x.unsqueeze_(0)\n    y_error: Tensor[int, L[1], L[2], L[3]] = x.unsqueeze_(0)\n\n    # pyre-ignore[9]: `unsqueeze_` is an in-place shape-transforming function. But Pyre cannot\n    # update a variable's shape type.\n    z: Tensor[int, L[1], L[2], L[3]] = x\n\n\ndef test_squeeze_() -> None:\n    x: Tensor[int, L[1], L[2], L[3]]\n    out: Tensor\n\n    y: Tensor[int, L[2], L[3]] = x.squeeze_(out=out)\n    # pyre-ignore[9]: Expected error.\n    y_error: Tensor[int, L[2], L[99]] = x.squeeze_()\n    y2: Tensor[int, L[2], L[3]] = x.squeeze_().squeeze_()\n\n    x2: Tensor[int, L[2], L[3], L[1], L[1]]\n    x3: Tensor[int, L[2], L[3], L[1]]\n    y3: Tensor[int, L[2], L[3]] = x2.squeeze_()\n    y4: Tensor[int, L[2], L[3]] = x3.squeeze_()\n    y5: Tensor[int, L[2], L[3]] = x.squeeze_(0)\n    y6: Tensor[int, L[2], L[3], L[1]] = x2.squeeze_(-1)\n\n\ndef test_squeeze() -> None:\n    x: Tensor[int, L[1], L[2], L[3]]\n    out: Tensor\n\n    y: Tensor[int, L[2], L[3]] = x.squeeze(out=out)\n    # pyre-ignore[9]: Expected error.\n    y_error: Tensor[int, L[2], L[99]] = x.squeeze()\n    y2: Tensor[int, L[2], L[3]] = x.squeeze().squeeze()\n\n    x2: Tensor[int, L[2], L[3], L[1], L[1]]\n    x3: Tensor[int, L[2], L[3], L[1]]\n    y3: Tensor[int, L[2], L[3]] = x2.squeeze()\n    y4: Tensor[int, L[2], L[3]] = x3.squeeze()\n    y5: Tensor[int, L[2], L[3]] = x.squeeze(0)\n    y6: Tensor[int, L[2], L[3], L[1]] = x2.squeeze(-1)\n\n\ndef test_repeat() -> None:\n    x: Tensor[int, L[2], L[3]]\n    y: Tensor[int, L[8], L[15]] = x.repeat(4, 5)\n    # pyre-fixme[9]\n    y2: Tensor[int, L[8], L[16]] = x.repeat(4, 5)\n\n    # TODO(T96315150): This is passing by coincidence right now.\n    y3: Tensor[int, L[4], L[10], L[18]] = x.repeat(4, 5, 6)\n    # pyre-ignore[9]: Doesn't error as expected because we have limited overloads.\n    y3_error: Tensor[int, L[4], L[10], L[99]] = x.repeat(4, 5, 6)\n\n    # pyre-ignore[9, 19]\n    not_yet_supported: Tensor[int, L[4], L[5], L[12], L[21]] = x.repeat(4, 5, 6, 7)\n\n    # Fewer dimensions than the Tensor. Should raise a different error.\n    x.repeat(2)\n\n    one_dimension: Tensor[int, L[2]]\n    y4: Tensor[int, L[8]] = x.repeat(4)\n    # pyre-ignore[9]\n    y4_error: Tensor[int, L[99]] = x.repeat(4)\n\n\ndef test_multiply() -> None:\n    x: Tensor[torch.int64, L[2], L[3]]\n\n    y: Tensor[torch.float32, L[2], L[3]] = x * 2\n    # pyre-fixme[9]: y_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: Tensor[torch.bool, L[2], L[99]] = x * 2\n\n    y2: Tensor[torch.float32, L[2], L[3]] = 2 * x\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y2_error: Tensor[torch.bool, L[2], L[99]] = 2 * x\n\n    y3: Tensor[torch.float32, L[2], L[3]] = x * 2.0\n    # pyre-fixme[9]: y3_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[4]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y3_error: Tensor[torch.float32, L[2], L[4]] = x * 2.0\n\n    z: Tensor[torch.int64, L[4], L[1], L[1]]\n    z_bad: Tensor[torch.int64, L[4], L[2], L[99]]\n    y4: Tensor[torch.int64, L[4], L[2], L[3]] = x * z\n    # pyre-fixme[2001]: Broadcast error at expression `x.__mul__(z_bad)`; types\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[4], typing_extensions.Literal[2],\n    #  typing_extensions.Literal[99]]` cannot be broadcasted together.\n    x * z_bad\n\n    x4: Tensor[torch.float32, L[2], L[3]]\n    x5: Tensor[torch.float32, L[2], L[3]]\n    x5_bad: Tensor[torch.float32, L[2], L[99]]\n    x4 *= x5\n    x4 *= 4\n    y5: Tensor[torch.float32, L[2], L[3]] = x5\n\n    # pyre-fixme[2001]: Broadcast error at expression `x4.__imul__(x5_bad)`; types\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[99]]` cannot be\n    #  broadcasted together.\n    x4 *= x5_bad\n\n\ndef test_floor_division() -> None:\n    x: Tensor[torch.int64, L[2], L[3]]\n    x2: Tensor[torch.int64, L[2], L[1]]\n    y: Tensor[torch.int64, L[2], L[3]] = x // 2\n    # pyre-fixme[9]: y_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: Tensor[torch.bool, L[2], L[99]] = x // 2\n\n    y2: Tensor[torch.int64, L[2], L[3]] = 2 // x\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y2_error: Tensor[torch.bool, L[2], L[99]] = 2 // x\n\n    y3: Tensor[torch.int64, L[2], L[3]] = x // x2\n\n    x3: Tensor[torch.float32, L[2], L[3]]\n    x4: Tensor[torch.float32, L[2], L[3]]\n    x4_bad: Tensor[torch.float32, L[2], L[99]]\n    x3 //= x4\n    x3 //= 4\n    y5: Tensor[torch.float32, L[2], L[3]] = x3\n\n    # pyre-fixme[2001]: Broadcast error at expression `x3.__ifloordiv__(x4_bad)`;\n    #  types `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[99]]` cannot be\n    #  broadcasted together.\n    x3 //= x4_bad\n\n\ndef test_division() -> None:\n    x: Tensor[torch.int64, L[2], L[3]]\n    x2: Tensor[torch.int64, L[2], L[1]]\n    y: Tensor[torch.float32, L[2], L[3]] = x / 2\n    # pyre-fixme[9]: y_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: Tensor[torch.bool, L[2], L[99]] = x / 2\n\n    y2: Tensor[torch.float32, L[2], L[3]] = 2 / x\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y2_error: Tensor[torch.bool, L[2], L[99]] = 2 / x\n\n    x3: Tensor[torch.float32, L[2], L[3]]\n    y3: Tensor[torch.float32, L[2], L[3]] = x3 / 2\n    y4: Tensor[torch.float32, L[2], L[3]] = 2 / x3\n\n    y5: Tensor[torch.float32, L[2], L[3]] = x / x2\n\n    x5: Tensor[torch.float32, L[2], L[3]]\n    x6: Tensor[torch.float32, L[2], L[3]]\n    x6_bad: Tensor[torch.float32, L[2], L[99]]\n    x5 /= x6\n    x5 /= 4\n    y6: Tensor[torch.float32, L[2], L[3]] = x5\n\n    # pyre-fixme[2001]: Broadcast error at expression `x5.__itruediv__(x6_bad)`;\n    #  types `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[99]]` cannot be\n    #  broadcasted together.\n    x5 /= x6_bad\n\n\ndef test_setitem() -> None:\n    x: Tensor[torch.int64, L[2], L[3]]\n    x[0, 0] = 1\n\n\ndef test_arange(n: N) -> None:\n    y: Tensor[torch.int64, L[5]] = torch.arange(5)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.int64,\n    #  typing_extensions.Literal[5]]`.\n    y_error: Tensor[torch.int64, L[99]] = torch.arange(5)\n    y2: Tensor[torch.int64, L[4]] = torch.arange(1, 5)\n    y3: Tensor[torch.int64, L[2]] = torch.arange(1, 6, 2)\n\n    y_float: Tensor[torch.float32, L[5]] = torch.arange(5, dtype=torch.float32)\n    y_float2: Tensor[torch.float32, L[2]] = torch.arange(1, 6, 2, dtype=torch.float32)\n\n    device: torch.device\n    y_generic: Tensor[torch.float32, N] = torch.arange(\n        0, n, device=device, dtype=torch.float32\n    )\n    # pyre-fixme[9]: Expected error.\n    y_generic_error: Tensor[torch.float32, L[99]] = torch.arange(\n        0, n, device=device, dtype=torch.float32\n    )\n\n\ndef test_embedding() -> None:\n    embedding = nn.Embedding(10, 20)\n    y: Tensor[torch.float32, L[10], L[20]] = embedding.weight\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[10], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[10],\n    #  typing_extensions.Literal[20]]`.\n    y_error: Tensor[torch.float32, L[10], L[99]] = embedding.weight\n\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y2: Tensor[torch.float32, L[2], L[3], L[4], L[20]] = embedding(x)\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.float32, typing_extensions.Liter...\n    y2_error: Tensor[torch.float32, L[2], L[3], L[4], L[99]] = embedding(x)\n\n    weight: Tensor[torch.float32, L[3], L[4]]\n    embedding2: nn.Embedding[L[3], L[4]] = nn.Embedding.from_pretrained(weight)\n    # pyre-fixme[9]: embedding2_error has type\n    #  `Embedding[typing_extensions.Literal[3], typing_extensions.Literal[99]]`; used\n    #  as `Embedding[typing_extensions.Literal[3], typing_extensions.Literal[4]]`.\n    embedding2_error: nn.Embedding[L[3], L[99]] = nn.Embedding.from_pretrained(weight)\n    y3: Tensor[torch.float32, L[2], L[3], L[4], L[4]] = embedding2(x)\n\n\ndef test_init_normal() -> None:\n    x: Tensor[torch.float32, L[5], L[10]]\n    y: Tensor[torch.float32, L[5], L[10]] = nn.init.normal_(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[5], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[5],\n    #  typing_extensions.Literal[10]]`.\n    y_error: Tensor[torch.float32, L[5], L[99]] = nn.init.normal_(x)\n\n\ndef test_view() -> None:\n    x: Tensor[torch.float32, L[4], L[4]]\n    y: Tensor[torch.float32, L[16]] = x.view(16)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32,\n    #  typing_extensions.Literal[16]]`.\n    y_error: Tensor[torch.float32, L[99]] = x.view(16)\n    # Should be an error because 4 * 4 != 99. Don't think this is going to be\n    # feasible any time soon.\n    y_error2: Tensor[torch.float32, L[99]] = x.view(99)\n    y_error3: Tensor[torch.float32, L[2], L[3], L[4], L[5]] = x.view(2, 3, 4, 5)\n\n    y2: Tensor[torch.float32, L[2], L[8]] = x.view(-1, 8)\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[8]]`.\n    y2_error: Tensor[torch.float32, L[2], L[99]] = x.view(-1, 8)\n\n    x3: Tensor[torch.float32, L[2], L[3], L[4]]\n    y3: Tensor[torch.float32, L[24]] = x3.view(-1)\n    y4: Tensor[torch.float32, L[8], L[3]] = x3.view(-1, 3)\n    # pyre-fixme[9]: y4_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[8],\n    #  typing_extensions.Literal[3]]`.\n    y4_error: Tensor[torch.float32, L[99], L[3]] = x3.view(-1, 3)\n    y5: Tensor[torch.float32, L[2], L[6], L[2]] = x3.view(2, -1, 2)\n\n    x4: Tensor[torch.float32, L[2], L[3], L[4], L[5]]\n    y6: Tensor[torch.float32, L[3], L[5], L[8]] = x4.view(3, 5, -1)\n\n\ndef test_reshape() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[24]] = torch.reshape(x, (-1,))\n    y2: Tensor[torch.float32, L[8], L[3]] = torch.reshape(x, (-1, 3))\n    # pyre-fixme[9]: y2_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[8],\n    #  typing_extensions.Literal[3]]`.\n    y2_error: Tensor[torch.float32, L[99], L[3]] = torch.reshape(x, (-1, 3))\n    y3: Tensor[torch.float32, L[6], L[2], L[2]] = torch.reshape(x, (-1, 2, 2))\n    y4: Tensor[torch.float32, L[2], L[6], L[2]] = torch.reshape(x, (2, -1, 2))\n    y5: Tensor[torch.float32, L[4], L[3], L[2]] = torch.reshape(x, (4, 3, 2))\n\n\ndef test_transpose() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4], L[5], L[6]]\n    y: Tensor[torch.float32, L[2], L[3], L[4], L[6], L[5]] = x.transpose(-2, -1)\n    y_function: Tensor[torch.float32, L[2], L[3], L[4], L[6], L[5]] = torch.transpose(\n        x, -2, -1\n    )\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: Tensor[torch.float32, L[2], L[4], L[99]] = x.transpose(-2, -1)\n\n    y2: Tensor[torch.float32, L[2], L[4], L[3], L[5], L[6]] = x.transpose(1, 2)\n    y3: Tensor[torch.float32, L[3], L[2], L[4], L[5], L[6]] = x.transpose(0, 1)\n    y4: Tensor[torch.float32, L[3], L[2], L[4], L[5], L[6]] = x.transpose(1, 0)\n    y5: Tensor[torch.float32, L[2], L[3], L[4], L[6], L[5]] = x.transpose(-1, -2)\n    not_yet_supported: Tensor[\n        torch.float32,\n        L[3],\n        L[2],\n        L[4],\n        L[5],\n        L[6],\n        # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n        #  `typing_extensions.Literal[4]`.\n    ] = x.transpose(1, 4)\n\n\ndef test_flatten() -> None:\n    x: Tensor[torch.float32, L[2], L[3]]\n    x_large: Tensor[torch.float32, L[2], L[3], L[4], L[5]]\n    y: Tensor[torch.float32, L[6]] = x.flatten()\n    y_default: Tensor[torch.float32, L[6]] = torch.flatten(x)\n    y_large: Tensor[torch.float32, L[120]] = x_large.flatten()\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32,\n    #  typing_extensions.Literal[6]]`.\n    y_error: Tensor[torch.float32, L[99]] = x.flatten()\n\n    z: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y2: Tensor[torch.float32, L[6], L[4]] = z.flatten(0, 1)\n    y2_keyword: Tensor[torch.float32, L[6], L[4]] = z.flatten(start_dim=0, end_dim=1)\n    y3: Tensor[torch.float32, L[2], L[12]] = z.flatten(1, 2)\n    y3_large: Tensor[torch.float32, L[2], L[12], L[5]] = x_large.flatten(1, 2)\n\n    y4: Tensor[torch.float32, L[2], L[3], L[20]] = x_large.flatten(2, 3)\n\n    x_6d: Tensor[torch.float32, L[2], L[3], L[4], L[5], L[6], L[7]]\n    y4_large: Tensor[torch.float32, L[2], L[3], L[20], L[6], L[7]] = x_6d.flatten(2, 3)\n\n    # Out of bounds.\n    # pyre-fixme[9]: y5_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[12]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[6]]`.\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 1st param but got\n    #  `typing_extensions.Literal[99]`.\n    y5_error: Tensor[torch.float32, L[2], L[12]] = x.flatten(99, 100)\n\n    x_0d: Tensor[torch.float32]\n    y_0d: Tensor[torch.float32, L[1]] = x_0d.flatten()\n\n\ndef test_empty() -> None:\n    x: Tuple[L[1], L[2], L[3]]\n    y: Tensor\n    device: torch.device\n\n    result1: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.empty(\n        *x,\n        device=device,\n        layout=torch.strided,\n        requires_grad=True,\n        out=y,\n        pin_memory=False,\n        memory_format=torch.memory_format(),\n    )\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad1: torch.Tensor[torch.float32, L[99], L[2], L[3]] = torch.empty(*x)\n\n    result2: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.empty(\n        *x, device=device, layout=torch.strided, requires_grad=True, out=y\n    )\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad2: torch.Tensor[torch.float32, L[99], L[2], L[3]] = torch.empty(\n        *x, device=device, layout=torch.strided, requires_grad=True, out=y\n    )\n\n    result4: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.empty(x)\n    result5: torch.Tensor[torch.float32, L[4]] = torch.empty(4)\n\n    result6: torch.Tensor[torch.int64, L[1], L[2], L[3]] = torch.empty(\n        x, dtype=torch.int64\n    )\n    result7: torch.Tensor[torch.int64, L[1], L[2], L[3]] = torch.empty(\n        *x, dtype=torch.int64\n    )\n\n\ndef test_empty_like() -> None:\n    x: torch.Tensor[torch.float32, L[1], L[2], L[3]]\n    out: Tensor\n    device: torch.device\n\n    y1: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.empty_like(\n        x, device=device, layout=torch.strided, requires_grad=True, out=out\n    )\n    # pyre-fixme[9]: Expected error.\n    y1_error: torch.Tensor[torch.float32, L[99], L[2], L[3]] = torch.empty_like(\n        x, device=device, layout=torch.strided, requires_grad=True, out=out\n    )\n    y2: torch.Tensor[torch.int64, L[1], L[2], L[3]] = torch.empty_like(\n        x,\n        dtype=torch.int64,\n        device=device,\n        layout=torch.strided,\n        requires_grad=True,\n        out=out,\n    )\n\n\ndef test_randn() -> None:\n    x: Tuple[L[1], L[2], L[3]]\n    y: Tensor\n    device: torch.device\n\n    result1: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.randn(\n        *x, device=device, layout=torch.strided, requires_grad=True, out=y\n    )\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad1: torch.Tensor[torch.float32, L[99], L[2], L[3]] = torch.randn(*x)\n\n    result2: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.randn(\n        *x, device=device, layout=torch.strided, requires_grad=True, out=y\n    )\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad2: torch.Tensor[torch.float32, L[99], L[2], L[3]] = torch.randn(\n        *x, device=device, layout=torch.strided, requires_grad=True, out=y\n    )\n\n    result4: torch.Tensor[torch.float32, L[1], L[2], L[3]] = torch.randn(x)\n    result5: torch.Tensor[torch.float32, L[4]] = torch.randn(4)\n\n    result6: torch.Tensor[torch.int64, L[1], L[2], L[3]] = torch.randn(\n        x, dtype=torch.int64\n    )\n    result7: torch.Tensor[torch.int64, L[1], L[2], L[3]] = torch.randn(\n        *x, dtype=torch.int64\n    )\n\n\ndef test_all() -> None:\n    x: torch.Tensor[torch.float32, L[1], L[2], L[3]]\n    device: torch.device\n\n    y: torch.Tensor[torch.bool, L[1]] = torch.all(x)\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.bool,\n    #  typing_extensions.Literal[1]]`.\n    y_error: torch.Tensor[torch.bool, L[99]] = torch.all(x)\n    y2: torch.Tensor[torch.bool, L[2], L[3]] = torch.all(x, dim=0)\n    y3: torch.Tensor[torch.bool, L[1], L[3]] = torch.all(x, dim=1)\n    y4: torch.Tensor[torch.bool, L[1]] = x.all()\n\n\ndef test_where() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n\n    good: Tuple[torch.LongTensor[int, int], torch.LongTensor[int, int]] = torch.where(x)\n    bad: Tuple[torch.LongTensor[int, int], torch.LongTensor[int, int], L[99]] = (\n        torch.where(x)\n    )\n\n    y: torch.Tensor[torch.float32, L[2], L[1]]\n    not_broadcastable: torch.Tensor[torch.float32, L[2], L[99]]\n\n    good: Tuple[torch.LongTensor[int, int], torch.LongTensor[int, int]] = torch.where(x)\n    good2: torch.Tensor[torch.float32, L[2], L[3]] = torch.where(x > 0, x, y)\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    bad2: torch.Tensor[torch.float32, L[2], L[99]] = torch.where(x > 0, x, y)\n    # pyre-fixme[2001]: Broadcast error at expression `torch.where(x > 0, x,\n    #  not_broadcastable)`; types `Tuple[typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]` and `Tuple[typing_extensions.Literal[2],\n    #  typing_extensions.Literal[99]]` cannot be broadcasted together.\n    z = torch.where(x > 0, x, not_broadcastable)\n\n\ndef test_getitem() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good1: torch.Tensor[torch.float32, L[3], L[4]] = x[0]\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[4]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[3],\n    #  typing_extensions.Literal[4]]`.\n    bad1: torch.Tensor[torch.float32, L[99], L[4]] = x[0]\n\n    good2: torch.Tensor[torch.float32, L[1], L[2], L[3], L[4]] = x[None]\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad2: torch.Tensor[torch.float32, L[99], L[2], L[3], L[4]] = x[None]\n\n    mask: torch.Tensor[torch.bool, L[2], L[3], L[4]]\n    good3: torch.Tensor[torch.float32, int] = x[mask]\n    # pyre-fixme[9]: bad3 has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32, int]`.\n    bad3: torch.Tensor[torch.float32, L[99]] = x[mask]\n\n    any1: Tuple[int, str, float] = x[2]\n    any2: Tuple[float, str, int] = x[2]\n\n\ndef test_expand() -> None:\n    x: torch.Tensor[torch.float32, L[1], L[2], L[3]]\n    shape: Tuple[L[4], L[1], L[3]]\n\n    good1: torch.Tensor[torch.float32, L[4], L[2], L[3]] = x.expand(shape)\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad1: torch.Tensor[torch.float32, L[99], L[2], L[3]] = x.expand(shape)\n    # pyre-fixme[2001]: Broadcast error at expression `x.expand((4, 99, 3))`; types `...\n    x.expand((4, 99, 3))\n\n    good2: torch.Tensor[torch.float32, L[4], L[2], L[3]] = x.expand(4, 1, 3)\n\n\ndef test_to() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good1: torch.Tensor[torch.int32, L[2], L[3], L[4]] = x.to(torch.int32)\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.int32, typing_extensions.Literal[99]...\n    bad1: torch.Tensor[torch.int32, L[99], L[3], L[4]] = x.to(torch.int32)\n\n    device: torch.device\n    good2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.to(device)\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad2: torch.Tensor[torch.float32, L[99], L[3], L[4]] = x.to(device)\n\n    y: torch.Tensor[torch.int32, L[2], L[3], L[4]]\n    good3: torch.Tensor[torch.float32, L[2], L[3], L[4]] = y.to(torch.float32, device)\n    # pyre-fixme[9]: bad3 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad3: torch.Tensor[torch.float32, L[99], L[3], L[4]] = y.to(torch.float32, device)\n\n\ndef test_Linear_to() -> None:\n    linear: nn.Linear[L[10], L[20]]\n    device: torch.device\n\n    linear.to(dtype=torch.int64, device=device)\n\n\ndef test_Module_eval() -> None:\n    module: nn.Module\n    module.eval()\n\n\ndef test_Module_train() -> None:\n    module: nn.Module\n    module.train(mode=True)\n    y: bool = module.training\n\n\ndef test_Linear_bias() -> None:\n    linear: nn.Linear[L[10], L[20]]\n\n    x: nn.Parameter = linear.bias\n\n\ndef test_sum() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y1: torch.Tensor[torch.float32, L[2], L[3]] = x.sum(-1, dtype=None)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[99], L[3]] = x.sum(-1, dtype=None)\n\n    y2: torch.Tensor[torch.float32, L[2], L[4]] = x.sum(-2)\n    y3: torch.Tensor[torch.float32] = x.sum()\n    y4: torch.Tensor[torch.float32, L[3], L[4]] = x.sum(0)\n    y5: torch.Tensor[torch.float32, L[2], L[4]] = x.sum(1)\n    y6: torch.Tensor[torch.float32] = torch.sum(x)\n\n\ndef test_cumsum() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good1: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.cumsum()\n    # pyre-fixme[9]: bad1 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad1: torch.Tensor[torch.float32, L[99], L[3], L[4]] = x.cumsum()\n\n    good2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.cumsum(dim=0)\n    # pyre-fixme[9]: bad2 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad2: torch.Tensor[torch.float32, L[99], L[3], L[4]] = x.cumsum(dim=0)\n\n    good3: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.cumsum(dtype=None)\n    # pyre-fixme[9]: bad3 has type `Tensor[torch.float32, typing_extensions.Literal[9...\n    bad3: torch.Tensor[torch.float32, L[99], L[3], L[4]] = x.cumsum(dtype=None)\n\n\ndef test_contiguous() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.contiguous()\n    # pyre-fixme[9]: bad has type `Tensor[torch.float32, typing_extensions.Literal[99...\n    bad: torch.Tensor[torch.float32, L[99], L[3], L[4]] = x.contiguous()\n\n\ndef test_diff() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good: torch.Tensor[torch.float32, L[2], L[3], L[3]] = torch.diff(x)\n    # pyre-fixme[9]: bad has type `Tensor[torch.float32, typing_extensions.Literal[99...\n    bad: torch.Tensor[torch.float32, L[99], L[3], L[3]] = torch.diff(x)\n    good2: torch.Tensor[torch.float32, L[1], L[3], L[4]] = torch.diff(x, dim=0)\n    good3: torch.Tensor[torch.float32, L[2], L[2], L[4]] = torch.diff(x, dim=1)\n    good4: torch.Tensor[torch.float32, L[2], L[3], L[3]] = torch.diff(x, dim=-1)\n    good5: torch.Tensor[torch.float32, L[2], L[2], L[4]] = torch.diff(x, dim=-2)\n    good6: torch.Tensor[torch.float32, L[2], L[2], L[4]] = x.diff(dim=-2)\n\n\ndef test_argsort() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good1: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.argsort(x)\n    # pyre-fixme[9]: bad1 has type `LongTensor[torch.float32, typing_extensions.Liter...\n    bad1: torch.Tensor[torch.float32, L[99], L[3], L[4]] = torch.argsort(x)\n\n    good2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.argsort(x, dim=0)\n    # pyre-fixme[9]: bad2 has type `LongTensor[torch.float32, typing_extensions.Liter...\n    bad2: torch.Tensor[torch.float32, L[99], L[3], L[4]] = torch.argsort(x, dim=0)\n\n    good3: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.argsort(\n        x, descending=True\n    )\n    # pyre-fixme[9]: bad3 has type `LongTensor[torch.float32, typing_extensions.Liter...\n    bad3: torch.Tensor[torch.float32, L[99], L[3], L[4]] = torch.argsort(\n        x, descending=True\n    )\n    good4: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.argsort(dim=-1)\n\n\ndef test_functional_pad() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good: torch.Tensor[torch.float32, L[2], L[3], L[5]] = nn.functional.pad(x, (1, 0))\n    bad: torch.Tensor[torch.float32, L[99], L[3], L[5]] = nn.functional.pad(x, (1, 0))\n    good2: torch.Tensor[torch.float32, L[2], L[10], L[7]] = nn.functional.pad(\n        x, (1, 2, 3, 4), \"constant\", value=0.0\n    )\n\n\ndef test_allclose() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[1]]\n    not_broadcastable: torch.Tensor[torch.float32, L[3], L[4]]\n    good: bool = torch.allclose(x, y, atol=0.0, rtol=0.0, equal_nan=True)\n    # This should complain about non-broadcastable tensors but we don't have a\n    # way to constrain two parameter types to be broadcastable.\n    should_error: bool = torch.allclose(x, not_broadcastable)\n\n\ndef test_new_ones() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n\n    y: torch.Tensor[torch.float32, L[8], L[9]] = x.new_ones((8, 9))\n    # pyre-fixme[9]: Expected error.\n    y_error: torch.Tensor[torch.float32, L[8], L[99]] = x.new_ones((8, 9))\n    y2: torch.Tensor[torch.int64, L[8], L[9]] = x.new_ones(\n        (8, 9), dtype=torch.int64, device=\"cuda\", requires_grad=True\n    )\n\n\ndef test_ones_like() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    device: torch.device\n\n    good: torch.Tensor[torch.int64, L[2], L[3]] = torch.ones_like(\n        x, dtype=torch.int64, device=device\n    )\n    # pyre-fixme[9]: bad has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.int64, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    bad: torch.Tensor[torch.int64, L[99], L[3]] = torch.ones_like(\n        x, dtype=torch.int64, device=device\n    )\n    bad2: torch.Tensor[torch.float32, L[2], L[3]] = torch.ones_like(\n        x,\n    )\n\n\ndef test_sparse_softmax() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.sparse.softmax(x, dim=-1)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[99], L[3]] = torch.sparse.softmax(x, dim=-1)\n    dtype: torch.int64\n    y2: torch.Tensor[torch.int64, L[2], L[3]] = torch.sparse.softmax(\n        x, dim=-1, dtype=dtype\n    )\n\n\ndef test_eye() -> None:\n    y: torch.Tensor[torch.int64, L[2], L[3]] = torch.eye(2, 3, dtype=torch.int64)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99], typing_extensions.Literal[3]]`; used as\n    #  `Tensor[torch.int64, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.int64, L[99], L[3]] = torch.eye(2, 3, dtype=torch.int64)\n    y2: torch.Tensor[torch.float32, L[3], L[3]] = torch.eye(3)\n\n\ndef test_adaptive_average_pool2d() -> None:\n    model: nn.AdaptiveAvgPool2d[L[5], L[7]] = nn.AdaptiveAvgPool2d((5, 7))\n    # pyre-fixme[9]: model_error has type\n    #  `AdaptiveAvgPool2d[typing_extensions.Literal[5],\n    #  typing_extensions.Literal[99]]`; used as\n    #  `AdaptiveAvgPool2d[typing_extensions.Literal[5], typing_extensions.Literal[7]]`.\n    model_error: nn.AdaptiveAvgPool2d[L[5], L[99]] = nn.AdaptiveAvgPool2d((5, 7))\n    model2: nn.AdaptiveAvgPool2d[L[5], L[5]] = nn.AdaptiveAvgPool2d(5)\n    # TODO(T100083794): This should be an error.\n    model2_error: nn.AdaptiveAvgPool2d[L[5], L[99]] = nn.AdaptiveAvgPool2d(5)\n    model3: nn.AdaptiveAvgPool2d[L[5], L[-1]] = nn.AdaptiveAvgPool2d((5, None))\n    # TODO(T100083794): This should be an error.\n    model3_error: nn.AdaptiveAvgPool2d[L[5], L[99]] = nn.AdaptiveAvgPool2d((5, None))\n\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[5], L[7]] = model(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[99], L[7]] = model(x)\n    y2: torch.Tensor[torch.float32, L[2], L[5], L[5]] = model2(x)\n    y3: torch.Tensor[torch.float32, L[2], L[5], L[4]] = model3(x)\n\n\ndef test_randperm() -> None:\n    y: torch.Tensor[torch.int64, L[10]] = torch.randperm(10, dtype=torch.int64)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.int64,\n    #  typing_extensions.Literal[10]]`.\n    y_error: torch.Tensor[torch.int64, L[99]] = torch.randperm(10, dtype=torch.int64)\n\n\ndef test_sqrt() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.sqrt(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[2], L[99]] = torch.sqrt(x)\n\n\ndef test_multinomial() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.multinomial(x, 3)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[2], L[99]] = torch.multinomial(x, 3)\n\n    x2: torch.Tensor[torch.float32, L[4]]\n    y2: torch.Tensor[torch.float32, L[3]] = torch.multinomial(x2, 3)\n    y2: torch.Tensor[torch.float32, L[3]] = x2.multinomial(3)\n\n\ndef test_bmm() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    matrix: torch.Tensor[torch.float32, L[2], L[4], L[5]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[5]] = torch.bmm(x, matrix)\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[5]] = x.bmm(matrix)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = torch.bmm(x, matrix)\n\n    bad_matrix: torch.Tensor[torch.float32, L[2], L[99], L[5]]\n    # Should raise an error but doesn't because we solve `L[99] <: M && L[4] <:\n    # M` to be M = int.\n    torch.bmm(x, bad_matrix)\n\n\ndef test_subtract() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[1]]\n    x2: torch.Tensor[torch.float32, L[2], L[1], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x - x2\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = x - x2\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x2 - x\n    y3: torch.Tensor[torch.float32, L[2], L[3], L[1]] = x - 42.0\n    y4: torch.Tensor[torch.float32, L[2], L[3], L[1]] = 42.0 - x\n\n    z: Any\n    # Should not error.\n    x - z\n\n    x5: Tensor[torch.float32, L[2], L[3]]\n    x6: Tensor[torch.float32, L[2], L[3]]\n    x6_bad: Tensor[torch.float32, L[2], L[99]]\n    x5 -= x6\n    x5 -= 4\n    y5: Tensor[torch.float32, L[2], L[3]] = x5\n\n    # pyre-fixme[2001]: Broadcast error at expression `x5.__isub__(x6_bad)`; types\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[99]]` cannot be\n    #  broadcasted together.\n    x5 -= x6_bad\n\n\ndef test_add() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[1]]\n    x2: torch.Tensor[torch.float32, L[2], L[1], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x + x2\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = x + x2\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x2 + x\n    y3: torch.Tensor[torch.float32, L[2], L[3], L[1]] = x + 42.0\n    y4: torch.Tensor[torch.float32, L[2], L[3], L[1]] = 42.0 + x\n\n    x5: Tensor[torch.float32, L[2], L[3]]\n    x6: Tensor[torch.float32, L[2], L[3]]\n    x6_bad: Tensor[torch.float32, L[2], L[99]]\n    x5 += x6\n    x5 += 4\n    y5: Tensor[torch.float32, L[2], L[3]] = x5\n\n    # pyre-fixme[2001]: Broadcast error at expression `x5.__iadd__(x6_bad)`; types\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3]]` and\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[99]]` cannot be\n    #  broadcasted together.\n    x5 += x6_bad\n\n\ndef test_torch_fft() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.complex64, L[2], L[3], L[4]] = torch.fft.fft(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.complex64, typing_extensions.Lite...\n    y_error: torch.Tensor[torch.complex64, L[2], L[3], L[99]] = torch.fft.fft(x)\n    y2: torch.Tensor[torch.complex64, L[2], L[3], L[4]] = torch.fft.fft(x, dim=-2)\n\n\ndef test_torch_real() -> None:\n    x: torch.Tensor[torch.complex64, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.real(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = torch.real(x)\n    x2: torch.Tensor[torch.complex128, L[2], L[3], L[4]]\n    y2: torch.Tensor[torch.float64, L[2], L[3], L[4]] = torch.real(x2)\n    bad: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    # pyre-fixme[6]: Expected `Tensor[torch.complex64, *torch.Ts]` for 1st param but\n    #  got `Tensor[torch.float32, int, int, int]`.\n    torch.real(bad)\n\n\ndef test_logical_and() -> None:\n    x: torch.Tensor[torch.complex64, L[2], L[1], L[4]]\n    x2: torch.Tensor[torch.float32, L[2], L[3], L[1]]\n    y: torch.Tensor[torch.bool, L[2], L[3], L[4]] = torch.logical_and(x, x2)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.bool, typing_extensions.Literal[2...\n    y_error: torch.Tensor[torch.bool, L[2], L[3], L[99]] = torch.logical_and(x, x2)\n    y2: torch.Tensor[torch.bool, L[2], L[3], L[4]] = x.logical_and(x2)\n    not_broadcastable: torch.Tensor[torch.float32, L[2], L[3], L[99]]\n    # pyre-fixme[2001]: Broadcast error at expression `torch.logical_and(x, not_broad...\n    torch.logical_and(x, not_broadcastable)\n\n    x3: torch.Tensor[torch.complex64, L[2], L[1], L[1]]\n    # In-place version.\n    x.logical_and_(x3)\n    # This is actually an error because the output type (2, 3, 4) is not\n    # assignable to x. But we can't catch that because the typechecker doesn't\n    # know this is an in-place operator. Leaving this as is for now.\n    x.logical_and_(x2)\n\n\ndef test_and() -> None:\n    x_bool: torch.Tensor[torch.bool, L[2], L[1], L[4]]\n    x_bool2: torch.Tensor[torch.bool, L[2], L[3], L[1]]\n    y3: torch.Tensor[torch.bool, L[2], L[3], L[4]] = x_bool & x_bool2\n\n    # This broadcasts to (2, 1, 4), which is assignable to x_bool.\n    x_bool3: torch.Tensor[torch.bool, L[2], L[1], L[1]]\n    x_bool &= x_bool3\n    # This broadcasts to (2, 3, 4), which is not assignable to x_bool.\n    # pyre-fixme[9]: x_bool has type `Tensor[torch.bool, typing_extensions.Literal[2]...\n    x_bool &= x_bool2\n\n    x: torch.Tensor[torch.complex64, L[2], L[1], L[4]]\n    x2: torch.Tensor[torch.float32, L[2], L[3], L[1]]\n    # pyre-fixme[58]: `&` is not supported for operand types\n    #  `Tensor[torch.complex64, int, int, int]` and `Tensor[torch.float32, int, int,\n    #  int]`.\n    x & x2\n\n\ndef test_linalg_pinv() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[2], L[4], L[3]] = torch.linalg.pinv(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[4], L[99]] = torch.linalg.pinv(x)\n    wrong_datatype: torch.Tensor[torch.bool, L[2], L[3], L[4]]\n    # pyre-fixme[6]: Expected `Tensor[Variable[torch.linalg.FloatOrDouble <:\n    #  [torch.float32, torch.float64, torch.complex64, torch.complex128]],\n    #  *torch.linalg.Ts, Variable[N1 (bound to int)], Variable[N2 (bound to int)]]` for\n    #  1st param but got `Tensor[torch.bool, int, int, int]`.\n    torch.linalg.pinv(wrong_datatype)\n\n    torch.linalg.pinv(x, hermitian=True)\n    # Last two dimensions have to be equal.\n    x_square: torch.Tensor[torch.float32, L[2], L[3], L[4], L[4]]\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4], L[4]] = torch.linalg.pinv(\n        x_square, hermitian=True\n    )\n\n\ndef test_linalg_qr() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3], L[3]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n    ] = torch.linalg.qr(x)\n    # pyre-fixme[9]: y_error has type `Tuple[Tensor[torch.float32, typing_extensions....\n    y_error: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3], L[99]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n    ] = torch.linalg.qr(x)\n    y2: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3], L[3]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n    ] = torch.linalg.qr(x, mode=\"complete\")\n\n\ndef test_torch_matmul() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[1], L[3], L[4]]\n    x2: torch.Tensor[torch.float32, L[1], L[5], L[4], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[5], L[3], L[3]] = torch.matmul(x, x2)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[5], L[3], L[99]] = torch.matmul(x, x2)\n    y2: torch.Tensor[torch.float32, L[2], L[5], L[3], L[3]] = x.matmul(x2)\n    y3: torch.Tensor[torch.float32, L[2], L[5], L[3], L[3]] = x.__matmul__(x2)\n\n    bad_x: torch.Tensor[torch.float32, L[1], L[5], L[99], L[3]]\n    torch.matmul(x, bad_x)\n\n    x_1d: torch.Tensor[torch.float32, L[3]]\n    x2_1d: torch.Tensor[torch.float32, L[3]]\n    y4: torch.Tensor[torch.float32] = torch.matmul(x_1d, x2_1d)\n    x3_1d_different: torch.Tensor[torch.float32, L[1]]\n    torch.matmul(x_1d, x3_1d_different)\n\n\ndef test_torch_optim() -> None:\n    block_parameters: Any\n    torch.optim.SGD(block_parameters, lr=1.0)\n\n\ndef test_torch_cuda() -> None:\n    torch.cuda.reset_peak_memory_stats()\n\n\ndef test_torch_profiler() -> None:\n    torch.profiler.profile()\n\n\ndef test_mse_loss() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    x2: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32] = nn.MSELoss(\n        size_average=True, reduce=True, reduction=\"mean\"\n    )(x, x2)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32]`.\n    y_error: torch.Tensor[torch.float32, L[99]] = nn.MSELoss()(x, x2)\n\n\ndef test_clip_grad_norm() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor = nn.utils.clip_grad_norm_(\n        x, max_norm=0.0, norm_type=0.0, error_if_nonfinite=True\n    )\n    # pyre-fixme[9]: y_error has type `int`; used as `Tensor[typing.Any,\n    #  *Tuple[typing.Any, ...]]`.\n    y_error: int = nn.utils.clip_grad_norm_(\n        x, max_norm=0.0, norm_type=0.0, error_if_nonfinite=True\n    )\n\n\ndef test_clip_grad_value() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    nn.utils.clip_grad_value_([x], clip_value=0.0)\n\n\ndef test_bitwise_not() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.bitwise_not(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[2], L[99]] = torch.bitwise_not(x)\n    y2: torch.Tensor[torch.float32, L[2], L[3]] = x.bitwise_not()\n    # In-place.\n    y3: torch.Tensor[torch.float32, L[2], L[3]] = x.bitwise_not_()\n    y4: torch.Tensor[torch.float32, L[2], L[3]] = ~x\n\n\ndef test_cdist() -> None:\n    x: torch.Tensor[torch.float32, L[5], L[1], L[2], L[3]]\n    x2: torch.Tensor[torch.float32, L[1], L[7], L[4], L[3]]\n    y: torch.Tensor[torch.float32, L[5], L[7], L[2], L[4]] = torch.cdist(x, x2)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[5], L[7], L[2], L[99]] = torch.cdist(x, x2)\n\n    not_broadcastable: torch.Tensor[torch.float32, L[99], L[1], L[2], L[3]]\n    # pyre-fixme[2001]: Broadcast error at expression `torch.cdist(x,\n    #  not_broadcastable)`; types `Tuple[typing_extensions.Literal[5],\n    #  typing_extensions.Literal[1]]` and `Tuple[typing_extensions.Literal[99],\n    #  typing_extensions.Literal[1]]` cannot be broadcasted together.\n    torch.cdist(x, not_broadcastable)\n\n\ndef test_random_manual_seed() -> None:\n    torch.random.manual_seed(42)\n\n\ndef test_clone() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.clone(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[2], L[99]] = torch.clone(x)\n    y2: torch.Tensor[torch.float32, L[2], L[3]] = x.clone()\n\n\ndef test_equal() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor[torch.bool, L[2], L[3]] = x == 42\n    # pyre-fixme[9]: y_error has type `Tensor[torch.bool,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.bool, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.bool, L[2], L[99]] = x == 42\n    # This doesn't return a Tensor as expected because `int.__eq__` accepts `object`.\n    y2: int = 42 == x\n\n    x2: torch.Tensor[torch.float32, L[2], L[1]]\n    x3: torch.Tensor[torch.float32, L[1], L[3]]\n    y3: torch.Tensor[torch.bool, L[2], L[3]] = x2 == x3\n\n\ndef test_diag_embed() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor = torch.diag_embed(x)\n\n\ndef test_unbind() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tuple[torch.Tensor[torch.float32, L[2], L[4]], ...] = torch.unbind(x, dim=1)\n    # pyre-fixme[9]: y_error has type `Tuple[Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]], ...]`; used as\n    #  `Tuple[Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[4]], ...]`.\n    y_error: Tuple[torch.Tensor[torch.float32, L[2], L[99]], ...] = torch.unbind(\n        x, dim=1\n    )\n    y2: Tuple[torch.Tensor[torch.float32, L[2], L[3]], ...] = torch.unbind(x, dim=-1)\n    y3: Tuple[torch.Tensor[torch.float32, L[3], L[4]], ...] = torch.unbind(x)\n    y4: Tuple[torch.Tensor[torch.float32, L[3], L[4]], ...] = x.unbind()\n\n\ndef test_size() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tuple[L[2], L[3], L[4]] = x.size()\n    # pyre-fixme[9]: y_error has type `Tuple[typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3], typing_extensions.Literal[99]]`; used as\n    #  `Tuple[typing_extensions.Literal[2], typing_extensions.Literal[3],\n    #  typing_extensions.Literal[4]]`.\n    y_error: Tuple[L[2], L[3], L[99]] = x.size()\n    y2: L[2] = x.size(0)\n    y3: L[3] = x.size(1)\n    y4: L[4] = x.size(-1)\n    y5: L[3] = x.size(-2)\n\n\ndef test_stack(\n    arbitary_length_tuple: Tuple[torch.Tensor[torch.float32, L[3], L[4], L[5]], ...],\n    variadic_tuple: Tuple[Unpack[Ts]],\n) -> None:\n    x: torch.Tensor[torch.float32, L[3], L[4], L[5]]\n    x_incompatible: torch.Tensor[torch.float32, L[3], L[4], L[99]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4], L[5]] = torch.stack((x, x))\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[4], L[99]] = torch.stack((x, x))\n    y_incompatible_tensors: torch.Tensor = torch.stack((x, x_incompatible))\n    y2: torch.Tensor[torch.float32, L[3], L[2], L[4], L[5]] = torch.stack((x, x), dim=1)\n    y3: torch.Tensor[torch.float32, L[3], L[3], L[4], L[5]] = torch.stack(\n        (x, x, x), dim=1\n    )\n    y4: torch.Tensor[torch.float32, L[3], L[3], L[4], L[5]] = torch.stack((x, x, x))\n\n    # Arbitrary-length tuples make it return an arbitrary Tensor.\n    y5: torch.Tensor = torch.stack(arbitary_length_tuple)\n    y6: torch.Tensor = torch.stack(variadic_tuple)\n\n\ndef test_repeat_interleave() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    repeats: torch.Tensor[torch.float32, L[2]]\n    y: torch.Tensor[torch.float32, L[72]] = torch.repeat_interleave(x, 3)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32,\n    #  typing_extensions.Literal[72]]`.\n    y_error: torch.Tensor[torch.float32, L[99]] = torch.repeat_interleave(x, 3)\n    y2: torch.Tensor[torch.float32, L[4], L[3], L[4]] = torch.repeat_interleave(\n        x, 2, dim=0\n    )\n    y3: torch.Tensor[torch.float32, L[2], L[6], L[4]] = torch.repeat_interleave(\n        x, 2, dim=1\n    )\n    y4: torch.Tensor[torch.float32, L[2], L[3], L[8]] = torch.repeat_interleave(\n        x, 2, dim=-1\n    )\n\n    # Too dynamic because the output shape depends on the contents of repeats.\n\n    y5: torch.Tensor[torch.float32, L[0], L[3], L[4]] = torch.repeat_interleave(\n        x, repeats, dim=0\n    )\n    y6: torch.Tensor[torch.float32, L[2], L[3], L[8]] = x.repeat_interleave(2, dim=-1)\n\n\ndef test_meshgrid() -> None:\n    x1: torch.Tensor[torch.float32, L[2]]\n    x2: torch.Tensor[torch.float32, L[3]]\n    x3: torch.Tensor[torch.float32, L[4]]\n    y: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n    ] = torch.meshgrid(x1, x2, x3)\n    # pyre-fixme[9]: y_error has type `Tuple[Tensor[torch.float32, typing_extensions....\n    y_error: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n        torch.Tensor[torch.float32, L[2], L[3], L[4]],\n        torch.Tensor[torch.float32, L[2], L[3], L[99]],\n    ] = torch.meshgrid(x1, x2, x3)\n\n    y2: Tuple[\n        torch.Tensor[torch.float32, L[2], L[3]],\n        torch.Tensor[torch.float32, L[2], L[3]],\n    ] = torch.meshgrid(x1, x2)\n    y3: Tuple[torch.Tensor[torch.float32, L[2]],] = torch.meshgrid(x1)\n\n    x4: Tensor\n    xs = tuple(x4 for _ in range(5))\n    y4: Tuple[torch.Tensor, ...] = torch.meshgrid(*xs)\n    xs2 = [x4 for _ in range(5)]\n    y5: Tuple[torch.Tensor, ...] = torch.meshgrid(*xs2)\n\n\ndef test_argmax() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.LongTensor[torch.int64] = torch.argmax(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.int64]`.\n    y_error: torch.LongTensor[torch.int64, L[99]] = torch.argmax(x)\n    y2: torch.LongTensor[torch.int64, L[3], L[4]] = torch.argmax(x, dim=0)\n    y3: torch.LongTensor[torch.int64, L[1], L[3], L[4]] = torch.argmax(\n        x, dim=0, keepdim=True\n    )\n    y4: torch.LongTensor[torch.int64, L[2], L[4]] = torch.argmax(x, dim=1)\n    y5: torch.LongTensor[torch.int64, L[2], L[1], L[4]] = torch.argmax(\n        x, dim=1, keepdim=True\n    )\n    y6: torch.LongTensor[torch.int64, L[2], L[3]] = torch.argmax(x, dim=2)\n    y7: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = torch.argmax(\n        x, dim=2, keepdim=True\n    )\n    y8: torch.LongTensor[torch.int64, L[2], L[3]] = torch.argmax(x, dim=-1)\n    y9: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = torch.argmax(\n        x, dim=-1, keepdim=True\n    )\n    y10: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = x.argmax(\n        dim=-1, keepdim=True\n    )\n\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n    #  `typing_extensions.Literal[3]`.\n    torch.argmax(x, dim=3)\n\n\ndef test_argmin() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.LongTensor[torch.int64] = torch.argmin(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.int64]`.\n    y_error: torch.LongTensor[torch.int64, L[99]] = torch.argmin(x)\n    y2: torch.LongTensor[torch.int64, L[3], L[4]] = torch.argmin(x, dim=0)\n    y3: torch.LongTensor[torch.int64, L[1], L[3], L[4]] = torch.argmin(\n        x, dim=0, keepdim=True\n    )\n    y4: torch.LongTensor[torch.int64, L[2], L[4]] = torch.argmin(x, dim=1)\n    y5: torch.LongTensor[torch.int64, L[2], L[1], L[4]] = torch.argmin(\n        x, dim=1, keepdim=True\n    )\n    y6: torch.LongTensor[torch.int64, L[2], L[3]] = torch.argmin(x, dim=2)\n    y7: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = torch.argmin(\n        x, dim=2, keepdim=True\n    )\n    y8: torch.LongTensor[torch.int64, L[2], L[3]] = torch.argmin(x, dim=-1)\n    y9: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = torch.argmin(\n        x, dim=-1, keepdim=True\n    )\n    y10: torch.LongTensor[torch.int64, L[2], L[3], L[1]] = x.argmin(\n        dim=-1, keepdim=True\n    )\n\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n    #  `typing_extensions.Literal[3]`.\n    torch.argmin(x, dim=3)\n\n\ndef test_mean() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32] = torch.mean(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.float32]`.\n    y_error: torch.Tensor[torch.float32, L[99]] = torch.mean(x)\n    y2: torch.Tensor[torch.float32, L[3], L[4]] = torch.mean(x, dim=0)\n    y3: torch.Tensor[torch.float32, L[1], L[3], L[4]] = torch.mean(\n        x, dim=0, keepdim=True\n    )\n    y4: torch.Tensor[torch.float32, L[2], L[4]] = torch.mean(x, dim=1)\n    y5: torch.Tensor[torch.float32, L[2], L[1], L[4]] = torch.mean(\n        x, dim=1, keepdim=True\n    )\n    y6: torch.Tensor[torch.float32, L[2], L[3]] = torch.mean(x, dim=2)\n    y7: torch.Tensor[torch.float32, L[2], L[3], L[1]] = torch.mean(\n        x, dim=2, keepdim=True\n    )\n    y8: torch.Tensor[torch.float32, L[2], L[3]] = torch.mean(x, dim=-1)\n    y9: torch.Tensor[torch.float32, L[2], L[3], L[1]] = torch.mean(\n        x, dim=-1, keepdim=True\n    )\n    y10: torch.Tensor[torch.float32, L[2], L[3], L[1]] = x.mean(dim=-1, keepdim=True)\n\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n    #  `typing_extensions.Literal[3]`.\n    torch.mean(x, dim=3)\n\n\ndef test_count_nonzero() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.int64] = torch.count_nonzero(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.int64,\n    #  typing_extensions.Literal[99]]`; used as `Tensor[torch.int64]`.\n    y_error: torch.Tensor[torch.int64, L[99]] = torch.count_nonzero(x)\n    y2: torch.Tensor[torch.int64, L[3], L[4]] = torch.count_nonzero(x, dim=0)\n    y3: torch.Tensor[torch.int64, L[2], L[4]] = torch.count_nonzero(x, dim=1)\n    y4: torch.Tensor[torch.int64, L[2], L[3]] = torch.count_nonzero(x, dim=2)\n    y5: torch.Tensor[torch.int64, L[2], L[3]] = x.count_nonzero(dim=-1)\n\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n    #  `typing_extensions.Literal[3]`.\n    torch.count_nonzero(x, dim=3)\n\n\ndef test_cat() -> None:\n    x1: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    x1_first_is_3: torch.Tensor[torch.float32, L[3], L[3], L[4]]\n    x1_first_is_4: torch.Tensor[torch.float32, L[4], L[3], L[4]]\n    x1_second_is_4: torch.Tensor[torch.float32, L[2], L[4], L[4]]\n    x1_second_is_5: torch.Tensor[torch.float32, L[2], L[5], L[4]]\n    x1_last_is_5: torch.Tensor[torch.float32, L[2], L[3], L[5]]\n    x1_last_is_6: torch.Tensor[torch.float32, L[2], L[3], L[6]]\n\n    # 2-element tuple.\n    y: torch.Tensor[torch.float32, L[5], L[3], L[4]] = torch.cat((x1, x1_first_is_3))\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[99], L[3], L[4]] = torch.cat(\n        (x1, x1_first_is_3)\n    )\n    y2: torch.Tensor[torch.float32, L[2], L[7], L[4]] = torch.cat(\n        (x1, x1_second_is_4), dim=1\n    )\n    y3: torch.Tensor[torch.float32, L[2], L[3], L[9]] = torch.cat(\n        (x1, x1_last_is_5), dim=-1\n    )\n    y3_shape_mismatch: torch.Tensor[torch.float32, Unpack[Tuple[Any, ...]]] = torch.cat(\n        (x1, x1_second_is_4), dim=-1\n    )\n\n    # 3-element tuple.\n    y4: torch.Tensor[torch.float32, L[9], L[3], L[4]] = torch.cat(\n        (x1, x1_first_is_3, x1_first_is_4)\n    )\n    y5: torch.Tensor[torch.float32, L[2], L[12], L[4]] = torch.cat(\n        (x1, x1_second_is_4, x1_second_is_5), dim=1\n    )\n    y6: torch.Tensor[torch.float32, L[2], L[3], L[15]] = torch.cat(\n        (x1, x1_last_is_5, x1_last_is_6), dim=-1\n    )\n\n    y_many_element_tuple: torch.Tensor[torch.float32, Unpack[Tuple[Any, ...]]] = (\n        torch.cat((x1, x1, x1, x1))\n    )\n    y_list: torch.Tensor[torch.float32, Unpack[Tuple[Any, ...]]] = torch.cat([x1, x1])\n\n\ndef test_sign() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.sign(x)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = torch.sign(x)\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.sign()\n\n\ndef test_diagonal() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4], L[5]]\n    y: torch.Tensor = torch.diagonal(x)\n\n\ndef test_diag() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n    y: torch.Tensor = torch.diag(x)\n\n\ndef test_module_list() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3]]\n\n    modules = nn.ModuleList([nn.AdaptiveAvgPool2d(0), nn.AdaptiveAvgPool2d(1)])\n    for module in modules:\n        y: Tensor = module(x)\n\n    z: int = len(modules)\n\n\ndef test_sparse_coo_tensor() -> None:\n    y: torch.Tensor[torch.float32, L[2], L[3]] = torch.sparse_coo_tensor(\n        torch.randn(5), [6, 7, 8], size=(2, 3)\n    )\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32,\n    #  typing_extensions.Literal[2], typing_extensions.Literal[99]]`; used as\n    #  `Tensor[torch.float32, typing_extensions.Literal[2],\n    #  typing_extensions.Literal[3]]`.\n    y_error: torch.Tensor[torch.float32, L[2], L[99]] = torch.sparse_coo_tensor(\n        torch.randn(5), [6, 7, 8], size=(2, 3)\n    )\n    y2: torch.Tensor = torch.sparse_coo_tensor(torch.randn(5), [6, 7, 8])\n\n\ndef test_max() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32] = torch.max(x)\n\n    y2: torch.Tensor[torch.float32, L[3], L[4]] = torch.max(x, dim=0).values\n    y2_indices: torch.Tensor[torch.int64, L[3], L[4]] = torch.max(x, dim=0).indices\n    y2_getitem: torch.Tensor[torch.int64, L[3], L[4]] = torch.max(x, dim=0)[1]\n    y3: torch.Tensor[torch.float32, L[1], L[3], L[4]] = torch.max(\n        x, dim=0, keepdim=True\n    ).values\n    y4: torch.Tensor[torch.float32, L[2], L[4]] = torch.max(x, dim=1).values\n    y5: torch.Tensor[torch.float32, L[2], L[1], L[4]] = torch.max(\n        x, dim=1, keepdim=True\n    ).values\n    y6: torch.Tensor[torch.float32, L[2], L[3]] = torch.max(x, dim=2).values\n    y7: torch.Tensor[torch.float32, L[2], L[3], L[1]] = torch.max(\n        x, dim=2, keepdim=True\n    ).values\n    y8: torch.Tensor[torch.float32, L[2], L[3]] = torch.max(x, dim=-1).values\n    y9: torch.Tensor[torch.float32, L[2], L[3], L[1]] = torch.max(\n        x, dim=-1, keepdim=True\n    ).values\n    y10: torch.Tensor[torch.float32, L[2], L[4]] = torch.max(x, dim=-2).values\n    y11: torch.Tensor[torch.float32, L[2], L[1], L[4]] = torch.max(\n        x, dim=-2, keepdim=True\n    ).values\n    y12: torch.Tensor[torch.float32, L[2], L[3], L[1]] = x.max(\n        dim=-1, keepdim=True\n    ).values\n\n    # pyre-fixme[6]: Expected `typing_extensions.Literal[0]` for 2nd param but got\n    #  `typing_extensions.Literal[3]`.\n    torch.max(x, dim=3).values\n\n\ndef test_einsum() -> None:\n    x: Tensor = torch.einsum(\"ii\", torch.randn(4, 4))\n\n\ndef test_type_as() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    x2: torch.Tensor[torch.int64, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.int64, L[2], L[3], L[4]] = x.type_as(x2)\n\n\ndef test_softmax() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = torch.softmax(x, dim=1)\n    # pyre-fixme[9]: y_error has type `Tensor[torch.float32, typing_extensions.Litera...\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = torch.softmax(x, dim=1)\n    y2: torch.Tensor[torch.int64, L[2], L[3], L[4]] = torch.softmax(\n        x, dim=1, dtype=torch.int64\n    )\n    y3: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.softmax(dim=1)\n\n\ndef test_conv2d() -> None:\n    x: Tensor[torch.float32, L[20], L[16], L[50], L[100]]\n\n    y7: Tensor[torch.float32, L[20], L[33], L[56], L[100]] = nn.Conv2d(\n        16, 33, (3, 5), padding=(4, 2), bias=False\n    )(x)\n    # pyre-fixme[9]: y7_error has type `Tensor[torch.float32, typing_extensions.Liter...\n    y7_error: Tensor[torch.float32, L[20], L[33], L[56], L[99]] = nn.Conv2d(\n        16, 33, (3, 5), padding=(4, 2)\n    )(x)\n\n    module: nn.Module = nn.Conv2d(16, 33, (3, 5), padding=(4, 2))\n\n\ndef test_nn_Parameter() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = nn.Parameter(x)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = nn.Parameter(x)\n\n\ndef test_torch_datatypes() -> None:\n    x: torch.float16\n    x2: torch.int\n\n\ndef test_norm() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    x_out: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y1: Tensor[torch.float32] = torch.norm(x)\n    y2: Tensor[torch.float32, L[3], L[4]] = torch.norm(x, dim=0, out=x_out, p=1)\n    # pyre-fixme[9]: Expected error.\n    y2_error: Tensor[torch.float32, L[3], L[99]] = torch.norm(x, dim=0)\n    y3: Tensor[torch.float32, L[1], L[3], L[4]] = torch.norm(x, dim=0, keepdim=True)\n\n\ndef test_rand() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    x_out: Tensor[torch.float32, L[2], L[3], L[4]]\n    device: torch.device\n\n    y1: Tensor[torch.float32, L[2], L[3], L[4]] = torch.rand(2, 3, 4)\n    # pyre-fixme[9]: Expected Error.\n    y1_error: Tensor[torch.float32, L[2], L[3], L[99]] = torch.rand(2, 3, 4)\n    y2: Tensor[torch.int64, L[2], L[3], L[4]] = torch.rand(\n        2,\n        3,\n        4,\n        dtype=torch.int64,\n        device=device,\n        layout=torch.strided,\n        out=x_out,\n        requires_grad=True,\n        generator=torch.default_generator,\n    )\n    y3: Tensor[torch.float32, L[2], L[3], L[4]] = torch.rand((2, 3, 4))\n\n\ndef test_randint() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    x_out: Tensor[torch.float32, L[2], L[3], L[4]]\n    device: torch.device\n\n    y1: Tensor[torch.int64, L[2], L[3], L[4]] = torch.randint(0, 3, (2, 3, 4))\n    # pyre-fixme[9]: Expected error.\n    y1_error: Tensor[torch.int64, L[2], L[3], L[99]] = torch.randint(0, 3, (2, 3, 4))\n    y2: Tensor[torch.int64, L[2], L[3], L[4]] = torch.randint(\n        3,\n        (2, 3, 4),\n        dtype=torch.int64,\n        device=device,\n        layout=torch.strided,\n        out=x_out,\n        requires_grad=True,\n        generator=torch.default_generator,\n    )\n\n\ndef test_zeros() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    x_out: Tensor[torch.float32, L[2], L[3], L[4]]\n    device: torch.device\n\n    y1: Tensor[torch.float32, L[2], L[3], L[4]] = torch.zeros(2, 3, 4)\n    # pyre-fixme[9]: Expected Error.\n    y1_error: Tensor[torch.float32, L[2], L[3], L[99]] = torch.zeros(2, 3, 4)\n    y2: Tensor[torch.int64, L[2], L[3], L[4]] = torch.zeros(\n        2,\n        3,\n        4,\n        dtype=torch.int64,\n        device=device,\n        layout=torch.strided,\n        out=x_out,\n        requires_grad=True,\n    )\n    y3: Tensor[torch.float32, L[2], L[3], L[4]] = torch.zeros((2, 3, 4))\n\n\ndef test_stride() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tuple[L[2], L[3], L[4]] = x.stride()\n    # pyre-fixme[9]: Expected error.\n    y_error: Tuple[L[2], L[3], L[99]] = x.stride()\n    y2: L[12] = x.stride(0)\n    y3: L[4] = x.stride(1)\n    y4: L[1] = x.stride(2)\n\n\ndef test_chunk() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tuple[\n        Tensor[torch.float32, L[2], L[3], L[2]], Tensor[torch.float32, L[2], L[3], L[2]]\n    ] = torch.chunk(x, 2, dim=-1)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tuple[\n        Tensor[torch.float32, L[2], L[3], L[99]],\n        Tensor[torch.float32, L[2], L[3], L[2]],\n    ] = torch.chunk(x, 2, dim=-1)\n    y2: Tuple[\n        Tensor[torch.float32, L[1], L[3], L[4]], Tensor[torch.float32, L[1], L[3], L[4]]\n    ] = torch.chunk(x, 2, dim=0)\n    y3: Tuple[\n        Tensor[torch.float32, L[1], L[3], L[4]], Tensor[torch.float32, L[1], L[3], L[4]]\n    ] = x.chunk(2, dim=0)\n\n\ndef test_abs() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = x.abs()\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = x.abs()\n\n\ndef test_enable_grad() -> None:\n    with torch.enable_grad():\n        pass\n\n\ndef test_normal() -> None:\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = torch.normal(\n        0, 1, size=(2, 3, 4), device=\"cuda\", requires_grad=True\n    )\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = torch.normal(\n        0, 1, size=(2, 3, 4), device=\"cuda\", requires_grad=True\n    )\n\n\ndef test_dim() -> None:\n    x0: Tensor[torch.float32]\n    x1: Tensor[torch.float32, L[2]]\n    x2: Tensor[torch.float32, L[2], L[3]]\n    x3: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: L[3] = x3.dim()\n    # pyre-fixme[9]: Expected error.\n    y_error: L[5] = x3.dim()\n    y2: L[0] = x0.dim()\n    y3: L[1] = x1.dim()\n    y4: L[2] = x2.dim()\n\n\ndef test_is_cuda() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: bool = x.is_cuda\n\n\ndef test_autograd_backward() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    torch.autograd.backward(x, x)\n\n\ndef test_linalg_norm() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2]] = torch.linalg.norm(x, dim=(-2, -1))\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[99]] = torch.linalg.norm(x, dim=(-2, -1))\n\n\ndef test_Sized() -> None:\n    x: torch.Size = torch.Size((2, 3, 4))\n\n\ndef test_initial_seed() -> None:\n    x: int = torch.initial_seed()\n\n\ndef test_log_softmax() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = torch.log_softmax(x, dim=1)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = torch.log_softmax(x, dim=1)\n    y2: Tensor[torch.int64, L[2], L[3], L[4]] = torch.log_softmax(\n        x, dtype=torch.int64, dim=1\n    )\n\n\ndef test_masked_select() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    mask: Tensor[torch.bool, L[2], L[3], L[4]]\n    out: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor = x.masked_select(mask, out=out)\n    y2: Tensor = torch.masked_select(x, mask, out=out)\n\n\ndef test__lt__() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.bool, L[2], L[3], L[4]] = x < 3.0\n\n\ndef test_pow() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = x**4\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = x**4\n\n\ndef test_item() -> None:\n    x: Tensor[torch.float32]\n    x2: Tensor[torch.float32, L[1]]\n\n    y: torch.float32 = x.item()\n    # pyre-fixme[9]: Expected error.\n    y_error: torch.int64 = x.item()\n    y2: torch.float32 = x.item()\n\n\ndef test_uniform_() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = nn.init.uniform_(x, a=1.0, b=2.0)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = nn.init.uniform_(\n        x, a=1.0, b=2.0\n    )\n\n\ndef test_kaiming_uniform_() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = nn.init.kaiming_uniform_(\n        x, a=1.0, mode=\"fan_in\", nonlinearity=\"leaky_relu\"\n    )\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = nn.init.kaiming_uniform_(x)\n\n\ndef test_constant_() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = nn.init.constant_(x, val=1.0)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = nn.init.constant_(x, val=1.0)\n\n\ndef test_leaky_relu() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = nn.LeakyReLU(\n        negative_slope=1.0, inplace=True\n    )(x)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = nn.LeakyReLU(\n        negative_slope=1.0, inplace=True\n    )(x)\n\n\ndef test_fft_fft2() -> None:\n    x: Tensor[torch.complex64, L[2], L[3], L[4]]\n    y: Tensor[torch.complex64, L[2], L[3], L[4]] = torch.fft.fft2(x)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.complex64, L[2], L[3], L[99]] = torch.fft.fft2(x)\n\n\ndef test_real() -> None:\n    x: Tensor[torch.complex64, L[2], L[3], L[4]]\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = x.real\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = x.real\n    x2: Tensor[torch.complex128, L[2], L[3], L[4]]\n    y2: Tensor[torch.float64, L[2], L[3], L[4]] = x2.real\n\n    not_complex: Tensor[torch.float64, L[2], L[3], L[4]]\n    # Should error but we don't have overloads for @property.\n    not_complex.real\n\n\ndef test_Tensor_init() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    # pyre-fixme[9]: Unexpected error because the constructor doesn't bind DType.\n    y: Tensor[torch.float32, L[2], L[3], L[4]] = Tensor((2, 3, 4), device=\"cuda\")\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[2], L[3], L[99]] = Tensor((2, 3, 4), device=\"cuda\")\n    y2: Tensor[torch.float32, L[2], L[3], L[4]] = Tensor(2, 3, 4, device=\"cuda\")\n    y3: Tensor[torch.float32, L[2], L[3], L[4]] = Tensor(x)\n\n\ndef test_reflection_pad2d() -> None:\n    module: nn.Module = nn.ReflectionPad2d(4)\n    x: Tensor[torch.float32, L[20], L[16], L[50], L[100]]\n\n    y: Tensor[torch.float32, L[20], L[16], L[58], L[108]] = nn.ReflectionPad2d(4)(x)\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.float32, L[20], L[16], L[58], L[99]] = nn.ReflectionPad2d(4)(\n        x\n    )\n\n\ndef test_half() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    good1: torch.Tensor[torch.float16, L[2], L[3], L[4]] = x.half(torch.memory_format())\n    # pyre-fixme[9]: Expected error.\n    bad1: torch.Tensor[torch.float16, L[99], L[3], L[4]] = x.half()\n\n\ndef test_is_contiguous() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n    y: bool = x.is_contiguous(torch.memory_format())\n\n\ndef test_scatter() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    # We don't really check for the shape of index or src.\n    index: torch.LongTensor[torch.float32, L[99]]\n    src: torch.Tensor[torch.float32, L[99], L[99]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.scatter(0, index, src)\n    # pyre-fixme[9]: Expected error.\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = x.scatter(0, index, src)\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.scatter(2, index, src)\n\n\ndef test_scatter_() -> None:\n    x: torch.Tensor[torch.float32, L[2], L[3], L[4]]\n\n    # We don't really check for the shape of index or src.\n    index: torch.LongTensor[torch.float32, L[99]]\n    src: torch.Tensor[torch.float32, L[99], L[99]]\n    y: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.scatter_(0, index, src)\n    # pyre-fixme[9]: Expected error.\n    y_error: torch.Tensor[torch.float32, L[2], L[3], L[99]] = x.scatter_(0, index, src)\n    y2: torch.Tensor[torch.float32, L[2], L[3], L[4]] = x.scatter_(2, index, src)\n\n\ndef test_bool() -> None:\n    x: Tensor[torch.float32, L[2], L[3], L[4]]\n\n    y: Tensor[torch.bool, L[2], L[3], L[4]] = x.bool()\n    # pyre-fixme[9]: Expected error.\n    y_error: Tensor[torch.bool, L[2], L[3], L[99]] = x.bool()\n"
  },
  {
    "path": "stubs/tqdm.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/triton/__init__.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/triton/language.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "stubs/triton/ops/blocksparse.pyi",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any\n\ndef __getattr__(name) -> Any: ...\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "tests/multiprocessing_utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport concurrent.futures\nimport gc\nimport multiprocessing\nimport os\nimport signal\nfrom tempfile import _TemporaryFileWrapper, NamedTemporaryFile\nfrom typing import Dict, List, Tuple\n\nimport torch\n\n\nclass SafeMpContext(multiprocessing.context.BaseContext):\n    def __init__(self) -> None:\n        self.mp_context = multiprocessing.get_context(\"spawn\")\n        self.processes: List[multiprocessing.context.SpawnProcess] = []\n\n    def Process(self, *args, **kwargs) -> multiprocessing.context.SpawnProcess:\n        p = self.mp_context.Process(*args, **kwargs)\n        p.daemon = True\n        self.processes.append(p)\n        return p\n\n    def kill_all_processes(self):\n        for p in self.processes:\n            p.terminate()\n            p.join(1)\n\n            # (https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Process.exitcode)\n            # Even though the python documentation seems to say that after joining the exitcode should\n            # become set, this is not what we have observed in practice. We therefore loop until it\n            # becomes set.\n            while p.exitcode is None:\n                p.kill()\n                p.join()\n\n            assert p.exitcode is not None, f\"{p} is still alive\"\n\n    def log_bad_exit_codes(self):\n        for rank, p in enumerate(self.processes):\n            if p.exitcode == 0:\n                continue\n            if p.exitcode < 0:\n                try:\n                    signal_desc = f\" (signal {signal.Signals(-p.exitcode).name})\"\n                except ValueError:\n                    signal_desc = \" (unrecognized signal)\"\n            else:\n                signal_desc = \"\"\n            print(\n                f\"Child process for rank #{rank} with PID {p.pid} exited with code {p.exitcode}{signal_desc}\"\n            )\n\n    def __getattr__(self, name: str):\n        return getattr(self.mp_context, name)\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        self.kill_all_processes()\n        self.log_bad_exit_codes()\n\n\ndef init_process_group(init_method: str, rank: int, world_size: int):\n    torch._C._set_print_stack_traces_on_fatal_signal(True)\n\n    if torch.cuda.device_count() >= world_size:\n        backend = \"nccl\"\n        torch.cuda.set_device(rank)\n    else:\n        # Use Gloo instead of NCCL so that we can run on a single GPU\n        backend = \"gloo\"\n\n    torch.distributed.init_process_group(\n        backend=backend,\n        world_size=world_size,\n        rank=rank,\n        init_method=init_method,\n    )\n\n\ndef _launch_subprocesses_fn_wrapper(\n    init_method: str,\n    rank: int,\n    world_size: int,\n    parent_env_vars: Dict[str, str],\n    user_fn,\n    args,\n    kwargs,\n):\n    # This function initializes the environment for spawned subprocesses by capturing and applying the current\n    # environment variables from the parent process. By clearing and then updating `os.environ` with `parent_env_vars`,\n    # we ensure that each spawned subprocess starts with an environment that mirrors the parent process at the time\n    # of job submission. This approach guarantees consistency across subprocesses, reflecting the latest state of the\n    # parent's environment variables even/especially when reusing the subprocesses for subsequent job executions.\n    os.environ.clear()\n    os.environ.update(parent_env_vars)\n\n    # Check if the process group is already initialized\n    if not torch.distributed.is_initialized():\n        init_process_group(init_method, rank, world_size)\n    try:\n        return user_fn(*args, **kwargs)\n    finally:\n        # should free all memory used by PyTorch in the subprocesses\n        gc.collect()\n        torch.cuda.empty_cache()\n\n\n# Global dictionary to keep track of executors and temporary files\nEXECUTORS_AND_FILES: Dict[\n    int, Tuple[_TemporaryFileWrapper, concurrent.futures.ProcessPoolExecutor]\n] = {}\n\n\ndef get_global_pool_allocator(\n    world_size: int,\n) -> Tuple[_TemporaryFileWrapper, concurrent.futures.ProcessPoolExecutor]:\n    global EXECUTORS_AND_FILES\n\n    if world_size not in EXECUTORS_AND_FILES:\n        rdv = NamedTemporaryFile(mode=\"w+b\", buffering=-1, delete=False)\n        mp_context = SafeMpContext()\n\n        executor = concurrent.futures.ProcessPoolExecutor(\n            max_workers=world_size, mp_context=mp_context\n        )\n\n        # Add the executor and temporary file to the global list\n        EXECUTORS_AND_FILES[world_size] = (rdv, executor)\n    else:\n        rdv, executor = EXECUTORS_AND_FILES[world_size]\n\n    return rdv, executor\n\n\nclass ProcessPoolExecutorManager:\n    def __init__(self, world_size: int):\n        self.world_size = world_size\n\n    def __enter__(self):\n        # when you start a subprocess you want to free memory used by PyTorch in the main process,\n        # so the subprocess can have memory\n        gc.collect()\n        torch.cuda.empty_cache()\n\n        self.rdv, self.executor = get_global_pool_allocator(self.world_size)\n        return self\n\n    def submit(self, fn, *args, **kwargs):\n        return self.executor.submit(fn, *args, **kwargs)\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        # One of the subprocesses jobs has failed\n        if exc_val:\n            # We want to avoid killing the processes while the executor was thinking that they were\n            # still up and healthy (as this may have unintended consequences, such as the executor\n            # restarting the processes, or reporting spurious errors).\n            # Set the internal state of the executor and call cancel() on each issued task that is\n            # not executing\n            self.executor.shutdown(wait=False, cancel_futures=True)\n\n            # Kill all remaining subprocesses\n            mp_context = self.executor._mp_context\n            mp_context.kill_all_processes()\n            mp_context.log_bad_exit_codes()\n\n            # We want to wait for all the futures to complete, so we need to shutdown twice\n            self.executor.shutdown(wait=True)\n\n            # Close the temporary file\n            self.rdv.close()\n\n            # Remove the executor from the global list.\n            # This will recreate it next time a test is requiring this world_size\n            assert self.world_size in EXECUTORS_AND_FILES\n            del EXECUTORS_AND_FILES[self.world_size]\n\n            print(\n                f\"Shutdown and remove the executor after subprocesses error. Executors cnt: {len(EXECUTORS_AND_FILES)}\"\n            )\n\n\ndef launch_subprocesses(world_size: int, fn, *args, **kwargs):\n    # This custom manager allows each test execution to enter/exit the following context.\n    # When entering the context, it creates/reuses a new/existing ProcessPoolExecutor with the given world size.\n    # The context also allows to detect an exception upon exit, in which case it will kill all spawned processes,\n    # delete the manager, recreate the manager upon following request and respawn processes.\n    with ProcessPoolExecutorManager(world_size) as manager:\n        futures = [\n            manager.submit(\n                _launch_subprocesses_fn_wrapper,\n                init_method=f\"file://{manager.rdv.name}\",\n                rank=rank,\n                world_size=world_size,\n                parent_env_vars=dict(os.environ),\n                user_fn=fn,\n                args=args,\n                kwargs=kwargs,\n            )\n            for rank in range(world_size)\n        ]\n        done, _ = concurrent.futures.wait(\n            futures, return_when=concurrent.futures.FIRST_EXCEPTION\n        )\n        for f in done:\n            f.result()\n"
  },
  {
    "path": "tests/readme_test_on_rocm.txt",
    "content": "\n   1. #> pip install -e ./\n\n   2. verify testing for generic fmha inference on ROCM\n\n      #> pytest tests/test_mem_eff_attention.py::test_forward\n\n   3. verify testing for decoder fmha inference on ROCM\n\n      #> pytest tests/test_mem_eff_attention.py::test_decoder\n      #> pytest tests/test_mem_eff_attention.py::test_splitk_decoder\n"
  },
  {
    "path": "tests/test_attention_patterns.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport itertools\n\nimport pytest\nimport torch\n\nimport xformers.components.attention.attention_patterns as AP\n\n\n# baseline implementations\ndef _local_1d_pattern(attn_size: int, window_size: int) -> torch.Tensor:\n    assert (\n        window_size % 2 == 1\n    ), \"The window size is assumed to be odd (counts self-attention + 2 wings)\"\n    h_win_size = window_size // 2\n\n    attn_shape = (attn_size, attn_size)\n    full_attn = torch.ones(attn_shape, dtype=torch.bool)\n\n    mask = torch.tril(full_attn, diagonal=h_win_size)\n    mask &= ~torch.tril(full_attn, diagonal=-(h_win_size + 1))\n    return mask\n\n\ndef _generate_2d_grid(H, W):\n    i = torch.arange(H)\n    j = torch.arange(W)\n    i, j = torch.meshgrid(i, j)\n    return i, j\n\n\ndef _horizontal_axial_2d_distance(H, W, p=2.0):\n    i, _ = _generate_2d_grid(H, W)\n    ij = i.reshape(-1, 1).float()\n    d = torch.cdist(ij, ij, p=p)\n    return d\n\n\ndef _vertical_axial_2d_distance(H, W, p=2.0):\n    _, j = _generate_2d_grid(H, W)\n    ij = j.reshape(-1, 1).float()\n    d = torch.cdist(ij, ij, p=p)\n    return d\n\n\ndef _local_2d_distance(H, W, p=2.0):\n    # axial is a special case with p=0 and distance=2\n    i, j = _generate_2d_grid(H, W)\n    ij = torch.stack([i.flatten(), j.flatten()], 1).float()\n    d = torch.cdist(ij, ij, p=p)\n    return d\n\n\ndef _local_2d_gaussian_distribution(H, W, sigma=1.0):\n    d = _local_2d_distance(H, W, p=2.0) ** 2\n    d = torch.exp(-0.5 * sigma ** (-2.0) * d)\n    return d\n\n\n@pytest.mark.parametrize(\"window_size\", [3, 7, 11])\n@pytest.mark.parametrize(\"attn_size\", [50, 51, 64])\ndef test_local_1d_pattern(attn_size, window_size):\n    mask = AP.local_1d_pattern(attn_size, window_size).float()\n    mask_ref = _local_1d_pattern(attn_size, window_size).float()\n    assert torch.allclose(mask, mask_ref)\n\n\n@pytest.mark.parametrize(\"p\", [0, 1, 2])\n@pytest.mark.parametrize(\"W\", [5, 7, 10])\n@pytest.mark.parametrize(\"H\", [5, 7, 10])\ndef test_horizontal_axial_2d_distance(H, W, p):\n    d = AP.horizontal_axial_2d_distance(H, W, p=p)\n    d_ref = _horizontal_axial_2d_distance(H, W, p=p)\n    assert torch.allclose(d, d_ref)\n\n\n@pytest.mark.parametrize(\"p\", [0, 1, 2])\n@pytest.mark.parametrize(\"W\", [5, 7, 10])\n@pytest.mark.parametrize(\"H\", [5, 7, 10])\ndef test_vertical_axial_2d_distance(H, W, p):\n    d = AP.vertical_axial_2d_distance(H, W, p=p)\n    d_ref = _vertical_axial_2d_distance(H, W, p=p)\n    assert torch.allclose(d, d_ref)\n\n\n@pytest.mark.parametrize(\"p\", [0, 1, 2])\n@pytest.mark.parametrize(\"W\", [5, 7, 10])\n@pytest.mark.parametrize(\"H\", [5, 7, 10])\ndef test_local_2d_distance(H, W, p):\n    d = AP.local_2d_distance(H, W, p=p)\n    d_ref = _local_2d_distance(H, W, p=p)\n    assert torch.allclose(d, d_ref)\n\n\n@pytest.mark.parametrize(\"sigma\", [0.5, 1, 2])\n@pytest.mark.parametrize(\"W\", [5, 7, 10])\n@pytest.mark.parametrize(\"H\", [5, 7, 10])\ndef test_local_2d_gaussian_distribution(H, W, sigma):\n    d = AP.local_2d_gausian_distribution(H, W, sigma=sigma)\n    d_ref = _local_2d_gaussian_distribution(H, W, sigma=sigma)\n    assert torch.allclose(d, d_ref)\n\n\n@pytest.mark.parametrize(\"window_size\", [2, 4])\n@pytest.mark.parametrize(\"W\", [8, 16])\n@pytest.mark.parametrize(\"H\", [8, 16])\ndef test_swin_attention_pattern(H, W, window_size):\n    # test non-shifted case\n    d = AP.swin_attention_pattern(H, W, window_size, shift_size=0)\n\n    # partition the self-attention into regions of window_size\n    # similar to the window_partition function from the original paper\n    h = H // window_size\n    w = W // window_size\n    d = d.reshape(h, window_size, w, window_size, h, window_size, w, window_size)\n\n    product = itertools.product(range(h), range(w))\n    for y, x in product:\n        # every region should fully attend to itself\n        assert torch.all(d[y, :, x, :, y, :, x, :])\n        for y2, x2 in product:\n            if y == y2 or x == x2:\n                continue\n            # different regions shouldn't attend between each other\n            assert torch.all(~d[y, :, x, :, y2, :, x2, :])\n\n    # test shifted case\n    # in the shifted case, the self-attention should be the same\n    # as in the non-shifted case, when we pad the inputs, apply the operations and then\n    # remove the padding from the result\n    d_shifted = AP.swin_attention_pattern(\n        H, W, window_size, shift_size=window_size // 2\n    )\n\n    # add padding and remove shift\n    h = H + window_size\n    w = W + window_size\n    d_padded = AP.swin_attention_pattern(h, w, window_size, shift_size=0)\n    d_padded = d_padded.reshape(h, w, h, w)\n\n    # remove padding elements\n    half_size = window_size // 2\n    s = slice(half_size, -half_size)\n    d_padded = d_padded[s, s, s, s].reshape(H * W, H * W)\n\n    assert torch.all(d_padded == d_shifted)\n\n\n@pytest.mark.parametrize(\"k\", [2, 3])\n@pytest.mark.parametrize(\"W\", [8, 15])\n@pytest.mark.parametrize(\"H\", [8, 15])\ndef test_dilated_2d_pattern(H, W, k):\n    d = AP.dilated_2d_pattern(H, W, k)\n    d = d.reshape(H, W, H, W)\n\n    product_HW = itertools.product(range(H), range(W))\n    product_kk = itertools.product(range(k), range(k))\n    for h, w in product_HW:\n        i = h % k\n        j = w % k\n        # every kth element is taken\n        assert torch.all(d[h, w][i::k, j::k])\n        for ii, jj in product_kk:\n            if ii == i and jj == j:\n                continue\n            # and the other elements are discarded\n            assert torch.all(~d[h, w][ii::k, jj::k])\n\n\ndef test_pattern_to_layout():\n    BLOCK = 16\n    SIZE = 128\n    LAYOUT_SIZE = SIZE // BLOCK\n\n    # All ones\n    mask1 = torch.ones((SIZE, SIZE), dtype=torch.bool)\n    layout1 = AP.pattern_to_layout(mask1, BLOCK)\n    ref1 = torch.ones((LAYOUT_SIZE, LAYOUT_SIZE), dtype=torch.long)\n    assert torch.allclose(layout1, ref1)\n\n    # Diagonal -> expect block diagonal\n    mask2 = torch.eye(SIZE, dtype=torch.bool)\n    layout2 = AP.pattern_to_layout(mask2, BLOCK)\n    ref2 = torch.eye(LAYOUT_SIZE, dtype=torch.long)\n    assert torch.allclose(layout2, ref2)\n\n    # Lower triangular, without the diagonal\n    # note that the layout will need to have the diagonal, else the coefficients close enough would not be computed\n    mask3 = torch.tril(torch.ones((SIZE, SIZE)), diagonal=-1).to(torch.bool)\n    layout3 = AP.pattern_to_layout(mask3, BLOCK)\n    ref3 = torch.tril(torch.ones((LAYOUT_SIZE, LAYOUT_SIZE)), diagonal=0).to(torch.long)\n    assert torch.allclose(layout3, ref3)\n\n    # Handle heads properly\n    mask = torch.cat((mask1, mask2, mask3))\n    layout = AP.pattern_to_layout(mask, BLOCK)\n    assert torch.allclose(layout, torch.cat((ref1, ref2, ref3)))\n\n    # Catch problematic dimensions\n    mask_off = torch.ones((SIZE + 3, SIZE), dtype=torch.bool)\n    with pytest.raises(AssertionError):\n        AP.pattern_to_layout(mask_off, BLOCK)\n\n\ndef test_alibi_pattern():\n    mask = AP.alibi_pattern(1e-3, (16, 128, 128))\n    # Minor, check that all the top left corners are True\n    assert torch.sum(mask[:, 0, 0]) == 16\n\n\ndef test_layout_to_pattern():\n    torch.allclose(\n        AP.layout_to_pattern(\n            layout=torch.Tensor([[[0, 1], [1, 0]], [[1, 0], [0, 1]]]), block_size=2\n        ),\n        torch.Tensor(\n            [\n                [\n                    [0.0, 0.0, 1.0, 1.0],\n                    [0.0, 0.0, 1.0, 1.0],\n                    [1.0, 1.0, 0.0, 0.0],\n                    [1.0, 1.0, 0.0, 0.0],\n                ],\n                [\n                    [1.0, 1.0, 0.0, 0.0],\n                    [1.0, 1.0, 0.0, 0.0],\n                    [0.0, 0.0, 1.0, 1.0],\n                    [0.0, 0.0, 1.0, 1.0],\n                ],\n            ]\n        ),\n    )\n"
  },
  {
    "path": "tests/test_checkpoint.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom contextlib import nullcontext\nfrom copy import deepcopy\n\nimport pytest\nimport torch\n\nimport xformers.ops\nfrom torch import nn\nfrom xformers.checkpoint import (\n    _optimize_runtime_with_given_memory,\n    checkpoint,\n    get_optimal_checkpoint_policy,\n    list_operators,\n    selective_checkpoint_wrapper,\n)\n\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\n_devices = [\"cpu\"]\ncuda_cap = (0, 0)\n\nif torch.cuda.is_available():\n    _devices.append(\"cuda\")\n    cuda_cap = torch.cuda.get_device_capability(_devices[1])\n\n\ndef _relu_policy(ctx, func, *args, **kwargs):\n    return func == torch.ops.aten.relu.default\n\n\ndef _all_policy(ctx, func, *args, **kwargs):\n    return True\n\n\n@pytest.mark.parametrize(\"policy_fn\", [None, [], _relu_policy, _all_policy])\n@pytest.mark.parametrize(\"input_requires_grad\", [True, False])\n@pytest.mark.parametrize(\"device\", _devices)\n@pytest.mark.parametrize(\"autocast\", [True, False])\ndef test_checkpoint(policy_fn, input_requires_grad, device, autocast):\n    def build_module():\n        return nn.Sequential(\n            nn.Linear(10, 10),\n            nn.ReLU(),\n            nn.Linear(10, 10),\n            nn.ReLU(),\n        ).to(device)\n\n    module = nn.ModuleList([build_module() for i in range(10)])\n\n    # Run model with and without checkpointing and verify gradients are\n    # equivalent, regardless of if inputs require grads or not.\n    module_copy = deepcopy(module)\n\n    inputs = torch.rand(32, 10, device=device)\n    inputs_copy = inputs.clone()\n    inputs.requires_grad_(input_requires_grad)\n    inputs_copy.requires_grad_(input_requires_grad)\n    out = inputs\n    out_copy = inputs_copy\n    with torch.autocast(device_type=device, enabled=autocast):\n        for i in range(10):\n            out = checkpoint(module[i], out, policy_fn=policy_fn)\n            out_copy = module_copy[i](out_copy)\n\n    assert torch.allclose(out, out_copy)\n    out.sum().backward()\n    out_copy.sum().backward()\n    for p, p_copy in zip(module.parameters(), module_copy.parameters()):\n        assert torch.allclose(p.grad, p_copy.grad)\n\n\n@pytest.mark.parametrize(\"policy_fn\", [None, [], _relu_policy, _all_policy])\n@pytest.mark.parametrize(\"input_requires_grad\", [True, False])\n@pytest.mark.parametrize(\"grad_mode\", [True, False])\ndef test_checkpoint_with_grad(policy_fn, input_requires_grad, grad_mode):\n    module = nn.Sequential(\n        nn.Linear(10, 10),\n        nn.ReLU(),\n        nn.Linear(10, 10),\n        nn.ReLU(),\n    )\n\n    # Run model with and without checkpointing and verify gradients are\n    # equivalent, regardless of if inputs require grads or not.\n    module_copy = deepcopy(module)\n\n    inputs = torch.rand(32, 10)\n    inputs_copy = inputs.clone()\n    inputs.requires_grad_(input_requires_grad)\n    inputs_copy.requires_grad_(input_requires_grad)\n    out = inputs\n    out_copy = inputs_copy\n    with torch.set_grad_enabled(grad_mode):\n        for i in range(10):\n            out = checkpoint(module, out, policy_fn=policy_fn)\n            out_copy = module_copy(out_copy)\n\n    assert torch.allclose(out, out_copy)\n\n\n@cuda_only\n@pytest.mark.parametrize(\"policy_fn\", [None, [], _relu_policy, _all_policy])\n@pytest.mark.parametrize(\"input_requires_grad\", [True, False])\n@pytest.mark.parametrize(\"device\", [\"cuda\"])\n@pytest.mark.parametrize(\"autocast\", [True, False])\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        xformers.ops.MemoryEfficientAttentionFlashAttentionOp,\n        (\n            xformers.ops.MemoryEfficientAttentionCutlassOp\n            if torch.version.cuda\n            else xformers.ops.MemoryEfficientAttentionCkOp\n        ),\n    ],\n)\ndef test_checkpoint_attention(policy_fn, input_requires_grad, device, autocast, op):\n    if (\n        op[0].CUDA_MINIMUM_COMPUTE_CAPABILITY > cuda_cap\n        or op[1].CUDA_MINIMUM_COMPUTE_CAPABILITY > cuda_cap\n    ):\n        pytest.skip(\"skipping operator not supported in this arch\")\n\n    if (\n        op is xformers.ops.MemoryEfficientAttentionFlashAttentionOp\n        and torch.version.hip\n    ):\n        pytest.skip(\"FlashAttentionOp is not supported on ROCM!\")\n\n    if op is xformers.ops.MemoryEfficientAttentionCkOp:\n        pytest.skip(\"Gradience is currently not supported by ck-tiled!\")\n\n    class Attn(nn.Module):\n        def forward(self, x):\n            out = xformers.ops.memory_efficient_attention(x, x, x, op=op)\n            return out + x\n\n    num_layers = 10\n    dtype = torch.float32 if autocast else torch.float16\n    modules = nn.Sequential(\n        *[\n            nn.Sequential(\n                nn.Linear(10, 64),\n                Attn(),\n                nn.ReLU(),\n                nn.Linear(64, 10),\n                nn.ReLU(),\n            )\n            .to(device)\n            .to(dtype)\n            for _ in range(num_layers)\n        ]\n    )\n\n    # Run model with and without checkpointing and verify gradients are\n    # equivalent, regardless of if inputs require grads or not.\n    modules_copy = deepcopy(modules)\n\n    inputs = torch.rand(32, 128, 10, dtype=dtype, device=device)\n    inputs_copy = inputs.clone()\n    inputs.requires_grad_(input_requires_grad)\n    inputs_copy.requires_grad_(input_requires_grad)\n    out = inputs\n    out_copy = inputs_copy\n    with torch.autocast(device_type=device, enabled=autocast):\n        for i in range(num_layers):\n            out = checkpoint(modules[i], out, policy_fn=policy_fn)\n            out_copy = modules_copy[i](out_copy)\n\n    assert torch.allclose(out, out_copy)\n    out.sum().backward()\n    out_copy.sum().backward()\n    for p, p_copy in zip(modules.parameters(), modules_copy.parameters()):\n        assert torch.allclose(\n            p.grad, p_copy.grad\n        ), f\"{(p.grad - p_copy.grad).abs().max()}\"\n\n    if input_requires_grad:\n        assert torch.allclose(inputs.grad, inputs_copy.grad)\n\n\ndef test_list_operators():\n    module = nn.Sequential(\n        nn.Linear(10, 10),\n        nn.ReLU(),\n        nn.Linear(10, 10),\n        nn.ReLU(),\n    )\n    inputs = torch.rand(32, 10)\n    operators = list_operators(module, inputs)\n    operators_str = [str(x) for x in operators]\n    ref = [\n        \"aten.t.default\",\n        \"aten.addmm.default\",\n        \"aten.relu.default\",\n        \"aten.detach.default\",\n        \"aten.t.default\",\n        \"aten.addmm.default\",\n        \"aten.relu.default\",\n        \"aten.detach.default\",\n    ]\n    assert operators_str == ref\n\n\n@pytest.mark.parametrize(\n    \"max_memory,optimal_soln\",\n    [\n        (0, torch.tensor([1, 0, 0, 0, 0, 0, 0, 0], dtype=torch.float64)),\n        (100, torch.tensor([1, 0, 0, 0, 0, 1, 0, 1], dtype=torch.float64)),\n        (120, torch.tensor([1, 0, 0, 1, 0, 0, 0, 0], dtype=torch.float64)),\n        (200, torch.tensor([1, 0, 1, 0, 0, 1, 0, 1], dtype=torch.float64)),\n        (220, torch.tensor([1, 0, 0, 1, 0, 1, 0, 1], dtype=torch.float64)),\n        (320, torch.tensor([1, 0, 1, 1, 0, 1, 0, 1], dtype=torch.float64)),\n        (420, torch.tensor([1, 1, 1, 1, 0, 1, 0, 1], dtype=torch.float64)),\n    ],\n)\ndef test_optimize_runtime_with_given_memory(max_memory, optimal_soln):\n    data = [\n        (\"aten.copy_\", 5, 0),\n        (\"aten.add\", 5, 100),\n        (\"aten.div\", 8, 100),\n        (\"aten.mm\", 15, 120),\n        (\"aten.native_dropout\", 15, 0),\n        (\"aten.linear\", 9, 100),\n        (\"aten.t\", 1, 0),\n        (\"aten.relu_\", 5, 0),\n    ]\n\n    inplace_ops = [(0, 0), (7, 5)]\n    view_like_ops = [6]\n    rand_ops = [4]\n\n    runtimes = torch.tensor([x[1] for x in data], dtype=torch.float64)\n    memory = torch.tensor([x[2] for x in data], dtype=torch.float64)\n\n    out = _optimize_runtime_with_given_memory(\n        memory,\n        runtimes,\n        max_memory,\n        view_like_ops,\n        inplace_ops,\n        rand_ops,\n        force_store_random=False,\n    )\n    torch.testing.assert_close(optimal_soln, out)\n\n\ndef _get_model_blocks(num_layers, dtype, device, inplace, random, first_inplace):\n    modules = []\n\n    class Add_(torch.nn.Module):\n        def forward(self, x):\n            return x.add_(1)\n\n    for _ in range(num_layers):\n        mods = [\n            nn.Linear(10, 10),\n            nn.CELU(inplace=inplace),\n        ]\n        if first_inplace:\n            mods.insert(0, Add_())\n        if random:\n            mods.append(nn.Dropout())\n        mods.append(nn.Linear(10, 10))\n        if random:\n            mods.append(nn.Dropout())\n        mods.append(nn.CELU(inplace=inplace))\n\n        modules.append(nn.Sequential(*mods).to(device).to(dtype))\n    return modules\n\n\nclass _Model(torch.nn.Module):\n    def __init__(self, blocks, policy_fn):\n        super().__init__()\n        self.blocks = torch.nn.ModuleList(blocks)\n        self.policy_fn = policy_fn\n\n    def forward(self, x):\n        for b in self.blocks:\n            x = checkpoint(b, x, policy_fn=self.policy_fn)\n        return x\n\n\n@cuda_only\n@pytest.mark.parametrize(\"device\", [\"cuda\"])\n@pytest.mark.parametrize(\"memory_budget\", [0, 0.03, 0.05, 0.1, 0.3, 0.5, 0.8, 1.0])\n@pytest.mark.parametrize(\"inplace\", [True, False])\n@pytest.mark.parametrize(\"random\", [True, False])\n@pytest.mark.parametrize(\"first_inplace\", [False])\ndef test_optimal_checkpoint_policy(\n    device, memory_budget, inplace, random, first_inplace\n):\n    if first_inplace and inplace:\n        pytest.skip(\"This case is degenerate and doesn't work with vanilla PyTorch\")\n    torch.manual_seed(42)\n    dtype = torch.float16\n    modules = _get_model_blocks(\n        3, dtype, device, inplace=inplace, random=random, first_inplace=first_inplace\n    )\n    inputs = torch.rand(32, 128, 10, dtype=dtype, device=device)\n\n    policy_fn = get_optimal_checkpoint_policy(\n        modules[0], inputs, memory_budget=memory_budget\n    )\n    model = _Model(modules, policy_fn)\n    model_ref = torch.nn.Sequential(*deepcopy(modules))\n\n    grad = torch.rand_like(inputs)\n\n    torch.manual_seed(42)\n    out = model(inputs.clone())\n    out.backward(grad)\n\n    torch.manual_seed(42)\n    out_ref = model_ref(inputs.clone())\n    out_ref.backward(grad)\n\n    torch.testing.assert_close(out, out_ref)\n\n    for p, p_ref in zip(model.parameters(), model_ref.parameters()):\n        torch.testing.assert_close(p.grad, p_ref.grad)\n\n\n@pytest.mark.skipif(True, reason=\"TODO[fmassa]: Broken on nightly\")\n@cuda_only\n@pytest.mark.parametrize(\"no_grad\", [False, True])\n@pytest.mark.parametrize(\"device\", [\"cuda\"])\n@pytest.mark.parametrize(\"memory_budget\", [0, 0.1, 0.3, 1.0])\n@pytest.mark.parametrize(\"inplace\", [False])\n@pytest.mark.parametrize(\"random\", [False])\n@torch._dynamo.config.patch(  # type: ignore\n    \"_experimental_support_context_fn_in_torch_utils_checkpoint\", True\n)\ndef test_selective_checkpoint_wrapper_compile(\n    device, no_grad, memory_budget, inplace, random\n):\n    torch.manual_seed(42)\n    dtype = torch.float16\n    modules = _get_model_blocks(\n        3, dtype, device, inplace=inplace, random=random, first_inplace=False\n    )\n    inputs = torch.rand(32, 128, 10, dtype=dtype, device=device)\n\n    model = torch.nn.Sequential(\n        *[selective_checkpoint_wrapper(b, memory_budget=memory_budget) for b in modules]\n    )\n    model = torch.compile(model)\n    model_ref = torch.nn.Sequential(*deepcopy(modules))\n\n    grad = torch.rand_like(inputs)\n\n    context = torch.no_grad() if no_grad else nullcontext()\n\n    with context:\n        torch.manual_seed(42)\n        out = model(inputs.clone())\n        if not no_grad:\n            out.backward(grad)\n\n        torch.manual_seed(42)\n        out_ref = model_ref(inputs.clone())\n        if not no_grad:\n            out_ref.backward(grad)\n\n    atol = 3e-4\n    rtol = 1e-3\n    torch.testing.assert_close(out, out_ref, atol=atol, rtol=rtol)\n\n    if no_grad:\n        return\n\n    for p, p_ref in zip(model.parameters(), model_ref.parameters()):\n        atol = 4e-4\n        rtol = 2e-3\n        torch.testing.assert_close(p.grad, p_ref.grad, atol=atol, rtol=rtol)\n"
  },
  {
    "path": "tests/test_fmha_flop_formula.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, Tuple, Type\n\nimport pytest\nimport torch\n\nimport xformers.ops\nfrom xformers.ops import fmha\n\nfrom .utils import disable_on_rocm, ref_attention_bmhk_for_test\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\nsm90_or_better_only = pytest.mark.skipif(\n    compute_capability < (9, 0), reason=\"requires sm90+\"\n)\n\n\n@disable_on_rocm\n@sm90_or_better_only\n@pytest.mark.parametrize(\n    \"B,Mq,Mkv,Hq,Hkv,Kqk,Kv\",\n    [\n        pytest.param(3, 13, 17, 7, 7, 128, 128, id=\"regular\"),\n        pytest.param(3, 13, 17, 7, 1, 128, 128, id=\"mqa\"),\n        # pytest.param(3, 13, 17, 21, 7, 128, 128, id=\"gqa\"),  # unsupported\n    ],\n)\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        (fmha.flash3.FwOp, fmha.flash3.BwOp),\n    ],\n    ids=lambda op: f\"{op[0].NAME}-{op[1].NAME}\",\n)\n@pytest.mark.parametrize(\"causal\", [False, True], ids=[\"full\", \"causal\"])\n@pytest.mark.parametrize(\"varseq\", [False, True], ids=[\"batched\", \"varseq\"])\n@pytest.mark.parametrize(\"worst_case\", [False, True], ids=[\"tight\", \"loose\"])\ndef test_flop_formula(\n    B: int,\n    Mq: int,\n    Mkv: int,\n    Hq: int,\n    Hkv: int,\n    Kqk: int,\n    Kv: int,\n    op: Tuple[Type[fmha.AttentionFwOpBase], Type[fmha.AttentionBwOpBase]],\n    causal: bool,\n    varseq: bool,\n    worst_case: bool,\n    monkeypatch,\n):\n    if (op[0] is fmha.flash3.FwOp and not op[0].is_available()) or (\n        op[1] is fmha.flash3.BwOp and not op[1].is_available()\n    ):\n        pytest.skip(\"Flash3 not available\")\n    dtype = torch.float16\n\n    if varseq:\n        B = 1\n    if causal:\n        Mkv = Mq\n\n    # No MQA/GQA in the reference impl\n    ref_q = torch.randn(\n        [B, Mq, Hq, Kqk], dtype=dtype, device=\"cuda\", requires_grad=True\n    )\n    ref_k = torch.randn(\n        [B, Mkv, Hq, Kqk], dtype=dtype, device=\"cuda\", requires_grad=True\n    )\n    ref_v = torch.randn(\n        [B, Mkv, Hq, Kv], dtype=dtype, device=\"cuda\", requires_grad=True\n    )\n\n    with torch.utils.flop_counter.FlopCounterMode(display=False) as fc:\n        ref_out = ref_attention_bmhk_for_test(ref_q, ref_k, ref_v, attn_bias=None)\n    ref_fwd_flops = fc.get_total_flops()\n    with torch.utils.flop_counter.FlopCounterMode(display=False) as fc:\n        ref_out.backward(torch.randn_like(ref_out))\n    ref_bwd_flops = fc.get_total_flops()\n\n    q = torch.randn([B, Mq, Hq, Kqk], dtype=dtype, device=\"cuda\", requires_grad=True)\n    k = torch.randn([B, Mkv, Hkv, Kqk], dtype=dtype, device=\"cuda\", requires_grad=True)\n    v = torch.randn([B, Mkv, Hkv, Kv], dtype=dtype, device=\"cuda\", requires_grad=True)\n\n    if Hkv == 1:\n        k = k.expand(-1, -1, Hq, -1)\n        v = v.expand(-1, -1, Hq, -1)\n    elif 1 < Hkv < Hq:\n        G = Hq // Hkv\n        q = q.unflatten(2, (Hkv, -1))\n        k = k.unflatten(2, (Hkv, -1)).expand(-1, -1, -1, G, -1)\n        v = v.unflatten(2, (Hkv, -1)).expand(-1, -1, -1, G, -1)\n\n    if varseq:\n        seqlens = ([5, Mq - 5], [5, Mkv - 5])\n\n    bias: Optional[fmha.attn_bias.AttentionBias]\n    if varseq and causal:\n        bias = fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask.from_seqlens(\n            *seqlens\n        )\n    elif varseq:\n        bias = fmha.attn_bias.BlockDiagonalMask.from_seqlens(*seqlens)\n    elif causal:\n        bias = fmha.attn_bias.LowerTriangularMask()\n    else:\n        bias = None\n\n    bias_for_flops: Optional[fmha.attn_bias.AttentionBias] = None\n    if worst_case:\n        if causal:\n            bias_for_flops = fmha.attn_bias.LowerTriangularMask()\n    else:\n        bias_for_flops = bias\n    if bias_for_flops is not None:\n        flops_div = Mkv * Mq\n        flops_mul = (\n            (bias_for_flops.materialize((Mq, Mkv), device=\"cpu\") == 0)\n            .int()\n            .sum()\n            .item()\n        )\n        ref_fwd_flops = ref_fwd_flops * flops_mul // flops_div\n        ref_bwd_flops = ref_bwd_flops * flops_mul // flops_div\n\n    if worst_case:\n        monkeypatch.setenv(\"XFORMERS_FLOP_FORMULA_WORST_CASE\", \"1\")\n    else:\n        # We disable it explicitly in case it's set in the user's environment\n        monkeypatch.setenv(\"XFORMERS_FLOP_FORMULA_WORST_CASE\", \"0\")\n\n    with torch.utils.flop_counter.FlopCounterMode(display=False) as fc:\n        out = xformers.ops.memory_efficient_attention(q, k, v, op=op, attn_bias=bias)\n    assert fc.get_total_flops() == ref_fwd_flops\n\n    with torch.utils.flop_counter.FlopCounterMode(display=False) as fc:\n        out.backward(torch.randn_like(out))\n    # Flash's backward recomputes the first matmul of the fwd.\n    assert fc.get_total_flops() == ref_bwd_flops + ref_fwd_flops / 2\n\n\ndef test_mask_nonzeros() -> None:\n    assert fmha.flash3.mask_non_zeros(13, 17, -1, 0) == 143\n    assert fmha.flash3.mask_non_zeros(13, 17, -1, -1) == 221\n\n    assert fmha.flash3.mask_non_zeros(8, 8, 32, 32) == 64\n    assert fmha.flash3.mask_non_zeros(8, 8, 4, 3) == 48\n    assert fmha.flash3.mask_non_zeros(8, 8, 4, 0) == 30\n    assert fmha.flash3.mask_non_zeros(8, 8, -1, -1) == 64\n"
  },
  {
    "path": "tests/test_fmha_merge_attentions.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport hashlib\nimport math\nfrom typing import Callable, List, Optional, Tuple, Type\n\nimport pytest\nimport torch\nfrom xformers.ops import fmha\nfrom xformers.ops.fmha.common import AttentionFwOpBase\nfrom xformers.ops.fmha.merge_training import (\n    memory_efficient_attention_partial_autograd,\n    merge_attentions_autograd,\n    Partial,\n)\n\nfrom .utils import assert_allclose, disable_on_rocm\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\nsm80_or_better_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm90+\"\n)\nsm90_or_better_only = pytest.mark.skipif(\n    compute_capability < (9, 0), reason=\"requires sm90+\"\n)\n\n\n# This temporary working is necessary because the MTIA test collection might not happen\n# on the same device as the device the tests are actually executed on. If test collection\n# is done on a device without MTIA, the supported masks will contain masks that MTIA support\n# and the corresponding tests will get collected. But when it comes time to actually run the\n# tests, the mask won't be supported because it is run on an actual MTIA device.\ndef get_supported_attn_bias_types(op):\n    supported_attn_bias_types = op.SUPPORTED_ATTN_BIAS_TYPES\n\n    try:\n        import mtia.host_runtime.torch_mtia.dynamic_library  # noqa\n\n        supported_attn_bias_types = [\n            b\n            for b in supported_attn_bias_types\n            if not issubclass(\n                b,\n                (\n                    fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n                    fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n                ),\n            )\n        ]\n    except (ImportError, OSError):\n        pass\n\n    return supported_attn_bias_types\n\n\n@disable_on_rocm\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        fmha.triton_splitk.FwOp,\n        fmha.flash.FwOp,\n        fmha.flash3.FwOp,\n        None,\n    ],\n    ids=lambda op: \"None\" if op is None else op.NAME,\n)\n@pytest.mark.parametrize(\"G,H\", [(1, 11), (7, 1), (1, 1), (7, 11), (None, 11)])\n@pytest.mark.parametrize(\n    \"write_lse\", (False, True), ids=lambda x: \"write_lse\" if x else \"\"\n)\n@pytest.mark.parametrize(\n    \"stack_inputs\", (False, True), ids=lambda x: \"stack_inputs\" if x else \"\"\n)\ndef test_merge_attentions_nobias(\n    write_lse: bool,\n    stack_inputs: bool,\n    op: Type[AttentionFwOpBase],\n    G: Optional[int],\n    H: int,\n):\n    \"\"\"\n    Merging the same attention twice shouldn't change anything.\n    This also tests the shape of the lse output of each permitted op.\n    \"\"\"\n    if op is fmha.flash3.FwOp and not op.is_available():\n        pytest.skip(\"Flash3 not available\")\n    B, Mq, K = 13, 3, 192\n    if op is fmha.triton_splitk.FwOp:\n        K = 128\n    case_name = str((write_lse, G, H, stack_inputs)).encode(\"ascii\")\n    many_keys = hashlib.md5(case_name).digest()[0] % 2\n    M = [5, 100000][many_keys]\n    if op is None or torch.bfloat16 in op.SUPPORTED_DTYPES:\n        dtype = torch.bfloat16\n    else:\n        dtype = next(iter(op.SUPPORTED_DTYPES))\n    if dtype == torch.float8_e4m3fn:\n        pytest.skip(\"float8 not supported\")\n    if G is None:\n        q = 3 * torch.rand(B, Mq, H, K, dtype=dtype, device=\"cuda\")\n        k = (3 * torch.rand(B, M, 1, K, dtype=dtype, device=\"cuda\")).expand(B, M, H, K)\n        v = (3 * torch.rand(B, M, 1, K, dtype=dtype, device=\"cuda\")).expand(B, M, H, K)\n    else:\n        q = 3 * torch.rand(B, Mq, G, H, K, dtype=dtype, device=\"cuda\")\n        k = (3 * torch.rand(B, M, G, 1, K, dtype=dtype, device=\"cuda\")).expand(\n            B, M, G, H, K\n        )\n        v = (3 * torch.rand(B, M, G, 1, K, dtype=dtype, device=\"cuda\")).expand(\n            B, M, G, H, K\n        )\n    out1, lse1 = fmha.memory_efficient_attention_partial(q, k, v, op=op)\n    assert out1.shape == q.shape\n    M_ceil = lse1.shape[-1]\n    assert M_ceil >= Mq\n    assert lse1.shape == (B, H, M_ceil) if G is None else (B, G, H, M_ceil)\n    lse1 = lse1[..., :Mq]\n\n    attn_chunks = [out1, out1]\n    lse_chunks = [lse1, lse1]\n    attn_chunks_ = torch.stack(attn_chunks) if stack_inputs else attn_chunks\n    lse_chunks_ = torch.stack(lse_chunks) if stack_inputs else lse_chunks\n    out, lse = fmha.merge_attentions(attn_chunks_, lse_chunks_, write_lse=write_lse)  # type: ignore\n    assert out.shape == out1.shape\n    assert_allclose(out1, out, rtol=1e-3, atol=1e-3, msg=\"out\")\n    if write_lse:\n        assert lse is not None\n        assert lse.shape[:-1] == lse1.shape[:-1]\n        assert_allclose(\n            lse1[..., :Mq] + math.log(2), lse[..., :Mq], rtol=1e-3, atol=1e-3, msg=\"lse\"\n        )\n    else:\n        assert lse is None\n\n\n@disable_on_rocm\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"dtype,op\",\n    [\n        (torch.bfloat16, fmha.triton_splitk.FwOp_S1),\n        # Cutlass's LSE is not consistent\n        # (torch.float32, fmha.cutlass.FwOp),\n        (torch.bfloat16, fmha.flash.FwOp),\n    ],\n    ids=lambda o: f\"{o.NAME}\" if hasattr(o, \"NAME\") else str(o),\n)\n@pytest.mark.parametrize(\"num_queries\", [1])\n@pytest.mark.parametrize(\"bmghk\", [True, False], ids=lambda x: \"bmghk\" if x else \"\")\ndef test_partial_paged(\n    dtype: torch.dtype, op: Type[AttentionFwOpBase], num_queries: int, bmghk: bool\n):\n    B = 128\n    N_H_L = 8\n    D_H = 128\n    page_size = 256\n    G = 2 if bmghk else 1\n    block_tables = torch.zeros((B, 1), dtype=torch.int32, device=\"cuda\")\n    torch.manual_seed(1)\n    output_dtype = torch.float32 if op.SUPPORTS_OUTPUT_DTYPE else None\n\n    B_T = num_queries * B\n\n    q = torch.randn((1, B_T, G, N_H_L, D_H), dtype=dtype, device=\"cuda\")\n    k = torch.randn((1, page_size, G, 1, D_H), dtype=dtype, device=\"cuda\")\n    v = torch.randn_like(k)\n    k = k.expand(1, page_size, G, N_H_L, D_H)\n    v = v.expand(1, page_size, G, N_H_L, D_H)\n    if not bmghk:\n        q = q[:, :, 0]\n        k = k[:, :, 0]\n        v = v[:, :, 0]\n\n    attn_bias = (\n        fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n            q_seqlen=[num_queries] * B,\n            kv_seqlen=[1] + ([100] * (B - 1)),\n            page_size=page_size,\n            block_tables=block_tables,\n        )\n    )\n\n    if attn_bias not in get_supported_attn_bias_types(op):\n        pytest.skip(\"Not supported bias\")\n\n    attn_chunk, lse_chunk = fmha.memory_efficient_attention_partial(\n        q,\n        k,\n        v,\n        attn_bias,\n        op=op,\n        output_dtype=output_dtype,\n    )\n    if bmghk:\n        assert attn_chunk.shape == (1, B_T, G, N_H_L, D_H)\n        assert lse_chunk.shape == (\n            1,\n            G,\n            N_H_L,\n            B_T,\n        ), f\"{lse_chunk.shape=}, {(1, G, N_H_L, B_T)=}\"\n    else:\n        assert attn_chunk.shape == (1, B_T, N_H_L, D_H)\n        assert lse_chunk.shape == (\n            1,\n            N_H_L,\n            B_T,\n        ), f\"{lse_chunk.shape=}, {(1, N_H_L, B_T)=}\"\n\n\n@disable_on_rocm\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"dtype,op\",\n    [\n        (torch.bfloat16, fmha.triton_splitk.FwOp_S1),\n        (torch.bfloat16, fmha.triton_splitk.FwOp_S32),\n        # Cutlass's LSE is not consistent\n        # (torch.float32, fmha.cutlass.FwOp),\n        (torch.bfloat16, fmha.flash.FwOp),\n    ],\n    ids=lambda o: f\"{o.NAME}\" if hasattr(o, \"NAME\") else str(o),\n)\n@pytest.mark.parametrize(\"num_queries\", [1, 2])\n@pytest.mark.parametrize(\"bmghk\", [True, False], ids=lambda x: \"bmghk\" if x else \"\")\n@pytest.mark.parametrize(\n    \"stack_inputs\", (False, True), ids=lambda x: \"stack_inputs\" if x else \"\"\n)\ndef test_merge_attentions_decoding(\n    dtype: torch.dtype,\n    op: Type[AttentionFwOpBase],\n    num_queries: int,\n    bmghk: bool,\n    stack_inputs: bool,\n):\n    \"\"\"\n    Compute decoding attention on chunks of K/V and merge them together.\n    Compare with computing attention on the whole K/V.\n    \"\"\"\n    MAX_T = 8192\n    B = 128\n    N_H_L = 8\n    D_H = 128\n    G = 2 if bmghk else 1\n    torch.manual_seed(1)\n    output_dtype = torch.float32 if op.SUPPORTS_OUTPUT_DTYPE else None\n\n    num_chunks = 10\n\n    chunk_starts = sorted(\n        torch.randint(low=1, high=MAX_T // 2, size=(num_chunks,)).tolist()\n    )\n    chunk_starts[0] = 0\n    chunk_starts.append(MAX_T)\n\n    # We construct sequences so that even the last chunk has a non-empty part of every sequence\n    # as long as the number of queries.\n    # Otherwise the corresponding LSE will be -inf and that'll propagate to the whole sum.\n    # It is possible to teach the kernel to ignore infinite LSEs, but in practical use cases\n    # of merging attention, e.g. a batch of sequences with a common prefix, this condition should be satisfied.\n    k_lens = torch.randint(\n        low=chunk_starts[-2] + num_queries, high=MAX_T, size=(B,)\n    ).tolist()\n    q_lens = [num_queries] * B\n    B_T = num_queries * B\n\n    q = torch.randn((1, B_T, G, N_H_L, D_H), dtype=dtype, device=\"cuda\")\n    k = torch.randn((B, MAX_T, G, 1, D_H), dtype=dtype, device=\"cuda\")\n    v = torch.randn_like(k)\n    if not bmghk:\n        q = q[:, :, 0]\n\n    # Compute per-chunk attention\n    chunks_output: List[Tuple[torch.Tensor, torch.Tensor]] = []\n    for i in range(num_chunks):\n        chunk_start, chunk_end = chunk_starts[i], chunk_starts[i + 1]\n        k_chunk = k[:, chunk_start:chunk_end, ...]\n        v_chunk = v[:, chunk_start:chunk_end, ...]\n        axk = k_chunk.reshape(-1, G, 1, D_H).expand(1, -1, G, N_H_L, D_H)\n        axv = v_chunk.reshape(-1, G, 1, D_H).expand(1, -1, G, N_H_L, D_H)\n        if not bmghk:\n            axk = axk[:, :, 0]\n            axv = axv[:, :, 0]\n\n        bias_type = fmha.attn_bias.BlockDiagonalPaddedKeysMask\n        if i + 1 == num_chunks:\n            bias_type = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask\n        attn_bias = bias_type.from_seqlens(\n            q_seqlen=q_lens,\n            kv_padding=chunk_end - chunk_start,\n            kv_seqlen=[max(min(x, chunk_end) - chunk_start, 0) for x in k_lens],\n        )\n\n        attn_chunk, lse_chunk = fmha.memory_efficient_attention_partial(\n            q,\n            axk,\n            axv,\n            attn_bias,\n            op=op,\n            output_dtype=output_dtype,\n        )\n        if bmghk:\n            assert attn_chunk.shape == (1, B_T, G, N_H_L, D_H)\n            assert lse_chunk.shape == (1, G, N_H_L, B_T)\n        else:\n            assert attn_chunk.shape == (1, B_T, N_H_L, D_H)\n            assert lse_chunk.shape == (1, N_H_L, B_T)\n        chunks_output.append((attn_chunk, lse_chunk))\n\n    # Merge attention from all chunks\n    attn_split = [attn_chunk for attn_chunk, _ in chunks_output]\n    lse_split = [lse_chunk for _, lse_chunk in chunks_output]\n    if stack_inputs:\n        attn_out, lse_out = fmha.merge_attentions(\n            torch.stack(attn_split), torch.stack(lse_split), output_dtype=dtype\n        )\n    else:\n        attn_out, lse_out = fmha.merge_attentions(\n            attn_split, lse_split, output_dtype=dtype\n        )\n    assert lse_out is not None\n\n    # Compute attention on the full K/V\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=q_lens,\n        kv_padding=MAX_T,\n        kv_seqlen=k_lens,\n    )\n    axk = k.view(1, -1, G, 1, D_H).expand(1, -1, G, N_H_L, D_H)\n    axv = v.view(1, -1, G, 1, D_H).expand(1, -1, G, N_H_L, D_H)\n    if not bmghk:\n        axk = axk[:, :, 0]\n        axv = axv[:, :, 0]\n    attn_full, lse_full = fmha.memory_efficient_attention_partial(\n        q,\n        axk,\n        axv,\n        attn_bias,\n        op=op,\n        output_dtype=output_dtype,\n    )\n\n    assert_allclose(\n        lse_out.to(lse_full.dtype), lse_full, rtol=1e-3, atol=1e-3, msg=\"lse\"\n    )\n    assert_allclose(\n        attn_out.to(attn_full.dtype), attn_full, rtol=1e-3, atol=1e-3, msg=\"out\"\n    )\n\n    attn_full2 = fmha.memory_efficient_attention_forward(\n        q,\n        axk,\n        axv,\n        attn_bias,\n        op=op,\n        output_dtype=output_dtype,\n    )\n    assert_allclose(attn_full2, attn_full, rtol=1e-3, atol=1e-3, msg=\"out2\")\n\n\n@disable_on_rocm\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"dtype,op\",\n    [\n        (torch.bfloat16, fmha.triton_splitk.FwOp_S1),\n        (torch.bfloat16, fmha.triton_splitk.FwOp_S32),\n    ],\n    ids=lambda o: f\"{o.NAME}\" if hasattr(o, \"NAME\") else str(o),\n)\n@pytest.mark.parametrize(\"gqa\", [False, True], ids=lambda x: \"gqa\" if x else \"\")\ndef test_merge_attentions_sharedinput(\n    dtype: torch.dtype,\n    op: Type[AttentionFwOpBase],\n    gqa: bool,\n):\n    \"\"\"\n    Compute decoding attention on chunks of K/V and merge them together.\n    Compare with computing attention on the whole K/V.\n    \"\"\"\n    MAX_T = 8192\n    N_H_L = 16\n    D_H = 128\n    G = 2\n    torch.manual_seed(1)\n    output_dtype = torch.float32 if op.SUPPORTS_OUTPUT_DTYPE else None\n\n    shared_length = 20\n    full_lengths = [30, 35, 40]\n\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[1, 1, 1],\n        kv_padding=MAX_T,\n        kv_seqlen=full_lengths,\n    )\n    attn_bias1 = fmha.attn_bias.BlockDiagonalPaddedKeysMask.from_seqlens(\n        q_seqlen=[2, 1],\n        kv_padding=MAX_T,\n        kv_seqlen=[shared_length, 0],\n    )\n    attn_bias2 = fmha.attn_bias.BlockDiagonalGappyKeysMask.from_seqlens(\n        q_seqlen=[1, 1, 1],\n        kv_seqstarts=[shared_length, MAX_T + shared_length, 2 * MAX_T, 3 * MAX_T],\n        kv_seqlen=[\n            full_lengths[0] - shared_length,\n            full_lengths[1] - shared_length,\n            full_lengths[2],\n        ],\n    )\n\n    q = torch.randn((1, 3, G, N_H_L, D_H), dtype=dtype, device=\"cuda\")\n    k = torch.randn((3, MAX_T, G, 1 if gqa else N_H_L, D_H), dtype=dtype, device=\"cuda\")\n    v = torch.randn_like(k)\n    k[1, :shared_length] = k[0, :shared_length]\n    v[1, :shared_length] = v[0, :shared_length]\n    k = k.flatten(end_dim=1)[None]\n    v = v.flatten(end_dim=1)[None]\n    k = k.expand((1, 3 * MAX_T, G, N_H_L, D_H))\n    v = v.expand((1, 3 * MAX_T, G, N_H_L, D_H))\n\n    attn_chunk1, lse_chunk1 = fmha.memory_efficient_attention_partial(\n        q,\n        k,\n        v,\n        attn_bias1,\n        op=op,\n        output_dtype=output_dtype,\n    )\n    assert attn_chunk1.shape == (1, 3, G, N_H_L, D_H)\n    assert lse_chunk1.shape == (1, G, N_H_L, 3)\n    if gqa:\n        attn_chunk1a, lse_chunk1a = fmha.memory_efficient_attention_partial(\n            q,\n            k.contiguous(),\n            v,\n            attn_bias1,\n            op=op,\n            output_dtype=output_dtype,\n        )\n        assert attn_chunk1a.shape == (1, 3, G, N_H_L, D_H)\n        assert lse_chunk1a.shape == (1, G, N_H_L, 3)\n        assert_allclose(\n            attn_chunk1a.nan_to_num(0, 0, 0), attn_chunk1.nan_to_num(0, 0, 0)\n        )\n        assert_allclose(lse_chunk1a.nan_to_num(0, 0, 0), lse_chunk1.nan_to_num(0, 0, 0))\n\n    attn_chunk2, lse_chunk2 = fmha.memory_efficient_attention_partial(\n        q,\n        k,\n        v,\n        attn_bias2,\n        op=op,\n        output_dtype=output_dtype,\n    )\n    assert attn_chunk2.shape == (1, 3, G, N_H_L, D_H)\n    assert lse_chunk2.shape == (1, G, N_H_L, 3)\n    # Merge attention from all chunks\n\n    attn_out, lse_out = fmha.merge_attentions(\n        [attn_chunk1, attn_chunk2],\n        [lse_chunk1, lse_chunk2],\n        output_dtype=dtype,  # type: ignore\n    )\n    assert lse_out is not None\n\n    # Compute attention on the full K/V\n    attn_full, lse_full = fmha.memory_efficient_attention_partial(\n        q,\n        k,\n        v,\n        attn_bias,\n        op=op,\n        output_dtype=output_dtype,\n    )\n    assert_allclose(\n        attn_out.to(attn_full.dtype), attn_full, rtol=1e-2, atol=2e-3, msg=\"out\"\n    )\n    assert_allclose(\n        lse_out.to(lse_full.dtype), lse_full, rtol=1e-3, atol=1e-3, msg=\"lse\"\n    )\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\"bmghk\", (False, True))\ndef test_merge_attentions_against_ref(bmghk: bool):\n    split_k = 16\n    B = 12\n    M = 137\n    G = 2 if bmghk else 1\n    N_H_L = 8\n    D_H = 128\n    dtype = torch.float32\n\n    attn_split = torch.randn([split_k, B, M, G, N_H_L, D_H], dtype=dtype, device=\"cuda\")\n    lse_split = torch.randn([split_k, B, G, N_H_L, M], dtype=dtype, device=\"cuda\")\n\n    if not bmghk:\n        attn_split = attn_split[:, :, :, 0]\n        lse_split = lse_split[:, :, 0]\n\n    attn_out_ref, lse_out_ref = _merge_attentions_ref(attn_split, lse_split)\n    attn_out, lse_out = fmha.merge_attentions(attn_split, lse_split)\n\n    torch.testing.assert_close(lse_out, lse_out_ref, rtol=1e-4, atol=1e-4)\n    torch.testing.assert_close(attn_out, attn_out_ref, rtol=1e-4, atol=1e-4)\n\n\ndef _merge_attentions_ref(attn_split, lse_split):\n    \"\"\"\n    attn_split: [split_k, B, M, (G,) H, Kq]\n    lse_split: [split_k, B, (G,) H, M]\n    \"\"\"\n    is_bmghk = len(attn_split.shape) == 6\n    if not is_bmghk:\n        attn_split = attn_split.unsqueeze(3)\n        lse_split = lse_split.unsqueeze(2)\n\n    lse_split = lse_split[..., None].moveaxis(4, 2)  # [split_k, B, M, G, H, 1]\n\n    lse_max, _ = torch.max(lse_split, dim=0)  # [B, M, G, H, 1]\n    sumexp_normalized = torch.exp(lse_split - lse_max)  # [split_k, B, M, G, H, 1]\n    denominator = sumexp_normalized.sum(dim=0)  # [B, M, G, H, 1]\n    numerator = (sumexp_normalized * attn_split).sum(dim=0)  # [B, M, G, H, K]\n\n    attn_out = numerator / denominator  # [B, M_ceil, G, H, Kq]\n    lse_out = lse_max + torch.log(denominator)\n    lse_out = lse_out.squeeze(4).permute(0, 2, 3, 1)  # [B, G, H, M]\n\n    if not is_bmghk:\n        attn_out = attn_out.squeeze(2)\n        lse_out = lse_out.squeeze(1)\n\n    return attn_out, lse_out\n\n\n@sm80_or_better_only\ndef test_merge_attention_with_compile() -> None:\n    op = fmha.flash3.FwOp\n    if not op.is_available():\n        pytest.skip(\"Op is not available\")\n    dtype = torch.bfloat16\n    B, M, H, K = 1, 256, 2, 128\n    q, k, v = [\n        (3 * torch.rand(B, M, H, K, dtype=dtype, device=\"cuda\")) for _ in range(3)\n    ]\n\n    out1, lse1 = fmha.memory_efficient_attention_partial(q, k, v, op=op)\n\n    def run_code() -> torch.Tensor:\n        out, _ = fmha.merge_attentions([out1], [lse1], write_lse=True)\n        return out\n\n    out_ref = run_code()\n    out_c = torch.compile(run_code, fullgraph=True)()\n\n    assert torch.allclose(out_ref, out_c, atol=1e-2, rtol=1e-2)\n\n    q.requires_grad_(True)\n    out1, lse1 = fmha.memory_efficient_attention_partial(q, k, v, op=op)\n    loss = fmha.merge_attentions([out1], [lse1])[0].sum()\n    with pytest.raises(\n        NotImplementedError,\n        match=\"Backward pass is not implemented for merge_attentions\",\n    ):\n        loss.backward()\n\n\n@sm80_or_better_only\ndef test_merge_training():\n    torch.manual_seed(1)\n    B, M, H, K = 1, 50, 1, 128\n    dtype = torch.bfloat16\n    op = (fmha.flash3.FwOp, fmha.flash3.BwOp)\n    q = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n    k = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n    v = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n    grad_out = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n\n    total_attention, (q_grad, k_grad, v_grad) = torch.autograd.functional.vjp(\n        fmha.memory_efficient_attention, (q, k, v), grad_out\n    )\n\n    def total_attn_via_Partial(q_, k_, v_):\n        return merge_attentions_autograd(\n            memory_efficient_attention_partial_autograd(q_, k_, v_)\n        )\n\n    attn, (q_grad_, k_grad_, v_grad_) = torch.autograd.functional.vjp(\n        total_attn_via_Partial, (q, k, v), grad_out\n    )\n\n    assert_allclose(attn, total_attention, rtol=1e-1, atol=1e-3, msg=\"out\")\n    assert_allclose(k_grad_, k_grad, rtol=1e-2, atol=1e-3, msg=\"dk_\")\n    assert_allclose(q_grad_, q_grad, rtol=1e-2, atol=1e-3, msg=\"dq_\")\n    assert_allclose(v_grad_, v_grad, rtol=1e-2, atol=1e-3, msg=\"dv_\")\n\n    def attn_via_Partial(q_, k_, v_):\n        split = M // 2\n        k1 = k_[:, :split]\n        k2 = k_[:, split:]\n        v1 = v_[:, :split]\n        v2 = v_[:, split:]\n\n        partial1 = memory_efficient_attention_partial_autograd(q_, k1, v1, op=op)\n        partial2 = memory_efficient_attention_partial_autograd(q_, k2, v2, op=op)\n        return merge_attentions_autograd(partial1, partial2)\n\n    merged, (q_grad_, k_grad_, v_grad_) = torch.autograd.functional.vjp(\n        attn_via_Partial, (q, k, v), grad_out\n    )\n    assert_allclose(\n        merged.to(total_attention.dtype),\n        total_attention,\n        rtol=1e-1,\n        atol=1e-3,\n        msg=\"out\",\n    )\n    assert_allclose(k_grad_, k_grad, rtol=0.1, atol=0.9, msg=\"dk\")\n    assert_allclose(q_grad_, q_grad, rtol=0.1, atol=0.9, msg=\"dq\")\n    assert_allclose(v_grad_, v_grad, rtol=0.1, atol=0.9, msg=\"dv\")\n\n\ndef _pad_seqdim(partial: Partial, left: int, right: int) -> Partial:\n    padding = (0, 0) * (3 if partial.is_bmghk() else 2) + (left, right)\n    return partial.apply(lambda x: torch.nn.functional.pad(x, padding))\n\n\ndef _slice(partial: Partial, a: int, b: int) -> Partial:\n    return partial.apply(lambda x: x[:, a:b])\n\n\n@sm80_or_better_only\ndef test_merge_training_compile():\n    torch.manual_seed(1)\n    B, M, H, K = 1, 50, 1, 128\n    dtype = torch.bfloat16\n    q = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n    k = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n    v = 3 * torch.rand((B, M, H, K), device=\"cuda\", dtype=dtype)\n\n    def f(\n        q: torch.Tensor,\n        k: torch.Tensor,\n        v: torch.Tensor,\n    ) -> torch.Tensor:\n        k1 = k[:, : M // 2]\n        k2 = k[:, M // 2 :]\n        v1 = v[:, : M // 2]\n        v2 = v[:, M // 2 :]\n        partial1 = memory_efficient_attention_partial_autograd(q, k1, v1)\n        partial2 = memory_efficient_attention_partial_autograd(q, k2, v2)\n        partial2 = _pad_seqdim(_pad_seqdim(partial2, 2, 3), -2, -3)\n        partial2 = _slice(_pad_seqdim(partial2, 2, 3), 2, -3)\n        merged = merge_attentions_autograd(partial1, partial2)\n        return merged.sum()\n\n    out, grads = torch.autograd.functional.vjp(f, (q, k, v))\n    outc, gradsc = torch.autograd.functional.vjp(\n        torch.compile(fullgraph=True)(f), (q, k, v)\n    )\n    assert_allclose(out, outc, rtol=1e-1, atol=1e-3)\n    for i, (grad, gradc) in enumerate(zip(grads, gradsc)):\n        assert_allclose(grad, gradc, rtol=1e-1, atol=1e-3, msg=f\"grad{i}\")\n\n    def g(q: torch.Tensor, k1: torch.Tensor, v1: torch.Tensor) -> torch.Tensor:\n        partial1 = memory_efficient_attention_partial_autograd(q, k1, v1)\n        return merge_attentions_autograd(partial1).sum()\n\n    out, grads = torch.autograd.functional.vjp(g, (q, k, v))\n    outc, gradsc = torch.autograd.functional.vjp(\n        torch.compile(fullgraph=True)(g), (q, k, v)\n    )\n    assert_allclose(out, outc, rtol=1e-1, atol=1e-3)\n    for i, (grad, gradc) in enumerate(zip(grads, gradsc)):\n        assert_allclose(grad, gradc, rtol=1e-1, atol=1e-3, msg=f\"grad{i}\")\n\n\n@sm80_or_better_only\ndef test_merge_training_zilch():\n    with pytest.raises(ValueError, match=\"No partials to merge\"):\n        merge_attentions_autograd()\n\n\n@sm80_or_better_only\ndef test_merge_training_undilate():\n    torch.manual_seed(1)\n\n    def undilate(factor: int) -> Callable[[torch.Tensor], torch.Tensor]:\n        \"\"\"\n        For a given factor, operate on BMHK attention output as follows:\n        If sequence length is M and there are H * factor heads,\n        redistribute so there are H heads and each\n        original sequence position is factor times inflated.\n        \"\"\"\n\n        def inner(x: torch.Tensor) -> torch.Tensor:\n            M = x.shape[1]\n            H = x.shape[2] // factor\n            return x.flatten(1, 2).unflatten(1, (M * factor, H))\n\n        return inner\n\n    B, M, F, H, K = 1, 2, 3, 5, 128\n    dtype = torch.bfloat16\n    q = 3 * torch.rand((B, M, F * H, K), device=\"cuda\", dtype=dtype)\n    k = 3 * torch.rand((B, M, F * H, K), device=\"cuda\", dtype=dtype)\n    v = 3 * torch.rand((B, M, F * H, K), device=\"cuda\", dtype=dtype)\n\n    bias = fmha.BlockDiagonalMask.from_seqlens([1] * M)\n    partial = memory_efficient_attention_partial_autograd(q, k, v, bias)\n\n    q = q.reshape(B, M * F, H, K)\n    k = k.reshape(B, M * F, H, K)\n    v = v.reshape(B, M * F, H, K)\n\n    bias = fmha.BlockDiagonalMask.from_seqlens([1] * (M * F))\n    expected = memory_efficient_attention_partial_autograd(q, k, v, bias)\n    undilated = partial.apply(undilate(F))\n\n    assert_allclose(undilated._attn, expected._attn)\n    assert_allclose(undilated._lse, expected._lse)\n"
  },
  {
    "path": "tests/test_fwbw_overlap.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Any\n\nimport pytest\nimport torch\nfrom xformers.fwbw_overlap import (\n    before_forward,\n    enter_comm,\n    enter_compute,\n    overlap_fw_bw,\n)\n\n\ndef test_fwbw_overlap() -> None:\n    class _JournalizedFunc(torch.autograd.Function):\n        @staticmethod\n        def forward(\n            ctx: Any, journal: list[str], name: str, tensor: torch.Tensor\n        ) -> Any:\n            ctx.journal, ctx.name = journal, name\n            journal.append(f\"{name}_F\")\n            return tensor\n\n        @staticmethod\n        def backward(ctx: Any, *gtensors) -> Any:\n            ctx.journal.append(f\"{ctx.name}_B\")\n            return None, None, *gtensors\n\n    def journalized_fn(d: list[str], n: str, x: torch.Tensor) -> torch.Tensor:\n        return _JournalizedFunc.apply(d, n, x)  # type: ignore\n\n    journal: list = []\n\n    def f(x: torch.Tensor) -> torch.Tensor:\n        x = x @ w1\n        x = journalized_fn(journal, \"compute1\", x)\n\n        compute_end_event, x = enter_comm(x, name=\"comm1\")  # type: ignore\n        x = journalized_fn(journal, \"comm1\", x)\n        x = enter_compute(compute_end_event, x, name=\"compute2\")\n\n        x = journalized_fn(journal, \"compute2\", x)\n        x = x @ w2\n\n        compute_end_event, x = enter_comm(x, name=\"comm2\")  # type: ignore\n        x = journalized_fn(journal, \"comm2\", x)\n        x = enter_compute(compute_end_event, x, name=\"end\")\n        return x\n\n    w1 = torch.randn([128, 128], device=\"cuda\", requires_grad=True)\n    w2 = torch.randn([128, 128], device=\"cuda\", requires_grad=True)\n    x = torch.randn([128, 128], device=\"cuda\", requires_grad=True)\n    gy = torch.randn_like(x)\n\n    # Disable everything\n    before_forward(False)\n    y = f(x)\n    y.backward(gy)\n    ref = dict(dw1=w1.grad, dw2=w2.grad, dx=x.grad)\n    w1.grad = w2.grad = x.grad = None\n\n    # Simple FW+BW\n    # enable FWBW overlap, but don't actually use it\n    before_forward(True)\n    y = f(x)\n    y.backward(gy)\n    assert torch.allclose(ref[\"dx\"], x.grad)  # type: ignore\n    assert torch.allclose(ref[\"dw1\"], w1.grad)  # type: ignore\n    assert torch.allclose(ref[\"dw2\"], w2.grad)  # type: ignore\n    w1.grad = w2.grad = x.grad = None\n\n    # Overlapped FW+BW\n    before_forward(True)\n    # warmup\n    y = f(x)\n    assert y is not None\n    assert y.requires_grad\n    # overlapped - BW first\n    journal.clear()\n    y = overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=1)\n    assert journal == [\n        \"compute1_F\",\n        \"comm2_B\",\n        \"comm1_F\",\n        \"compute2_B\",\n        \"compute2_F\",\n        \"comm1_B\",\n        \"comm2_F\",\n        \"compute1_B\",\n    ]\n    # overlapped - FW first\n    journal.clear()\n    y = overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=0)\n    assert journal == [\n        \"compute1_F\",\n        \"comm1_F\",\n        \"comm2_B\",\n        \"compute2_F\",\n        \"compute2_B\",\n        \"comm2_F\",\n        \"comm1_B\",\n        \"compute1_B\",\n    ]\n    # cooldown\n    y.backward(gy)\n    assert torch.allclose(3 * ref[\"dx\"], x.grad)  # type: ignore\n    assert torch.allclose(3 * ref[\"dw1\"], w1.grad)  # type: ignore\n    assert torch.allclose(3 * ref[\"dw2\"], w2.grad)  # type: ignore\n\n\ndef test_fwbw_nothing_to_overlap() -> None:\n    def f(x: torch.Tensor) -> torch.Tensor:\n        x = x * x\n        return x\n\n    x = torch.randn([128], device=\"cuda\", requires_grad=True)\n    gy = torch.randn([128], device=\"cuda\")\n\n    before_forward(True)\n    y = f(x)\n    y = overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=1)\n    y = overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=0)\n\n\nclass ExceptionInBW(Exception):\n    pass\n\n\nclass ExceptionInBWOp(torch.autograd.Function):\n    @staticmethod\n    def forward(ctx: Any, x: torch.Tensor) -> torch.Tensor:\n        return x\n\n    @staticmethod\n    def backward(ctx: Any, gx: torch.Tensor) -> torch.Tensor:  # type: ignore\n        raise ExceptionInBW()\n\n\ndef test_exception_in_bw_pass() -> None:\n    def f(x: torch.Tensor) -> torch.Tensor:\n        x = x * x\n        compute_end_event, x = enter_comm(x, name=\"comm1\")  # type: ignore\n        x = ExceptionInBWOp.apply(x)  # type: ignore\n        x = enter_compute(compute_end_event, x, name=\"compute2\")\n        return x * x\n\n    x = torch.randn([128], device=\"cuda\", requires_grad=True)\n    gy = torch.randn([128], device=\"cuda\")\n\n    before_forward(True)\n    y = f(x)\n    with pytest.raises(ExceptionInBW):\n        overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=1)\n    y = f(x)\n    with pytest.raises(ExceptionInBW):\n        overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=0)\n\n\ndef test_exception_in_first_bw_pass() -> None:\n    def f(x: torch.Tensor) -> torch.Tensor:\n        x = x * x\n        _, x = enter_comm(x, name=\"comm1\")  # type: ignore\n        return ExceptionInBWOp.apply(x)  # type: ignore\n\n    x = torch.randn([128], device=\"cuda\", requires_grad=True)\n    gy = torch.randn([128], device=\"cuda\")\n\n    before_forward(True)\n    y = f(x)\n    with pytest.raises(ExceptionInBW):\n        overlap_fw_bw(lambda: f(x), lambda: y.backward(gy), initial_bw_chunks=0)\n"
  },
  {
    "path": "tests/test_indexing.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport random\n\nimport pytest\nimport torch\n\nimport xformers.ops as xops\nfrom xformers.ops import indexing\n\nfrom .utils import assert_allclose\n\n\n@pytest.mark.skipif(\n    not indexing.ScaledIndexAddFw.is_available(), reason=\"not available\"\n)\n@pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\n@pytest.mark.parametrize(\"with_scaling\", [False, True])\n@pytest.mark.parametrize(\n    \"out_shape\", [(48, 1, 257 * 1536), (48, 257, 1536), (192, 50, 1536)]\n)\ndef test_scaled_index_add(out_shape, with_scaling: bool) -> None:\n    torch.manual_seed(0)\n    alpha = 0.73\n    dtype = torch.float16\n    B_out, M, D = out_shape\n    B_src = int(B_out * 0.6)\n\n    inp = torch.randn([B_out, M, D], device=\"cuda\", dtype=dtype, requires_grad=True)\n    src = torch.randn([B_src, M, D], device=\"cuda\", dtype=dtype, requires_grad=True)\n    TENSORS = {\"inp\": inp, \"src\": src}\n\n    index_py = [i for i in range(src.shape[0])]\n    random.Random(B_out).shuffle(index_py)\n    index = torch.tensor(index_py, dtype=torch.int64, device=\"cuda\")\n\n    if with_scaling:\n        scaling = torch.randn([D], device=\"cuda\", dtype=dtype, requires_grad=True)\n        TENSORS[\"scaling\"] = scaling\n        ref_src_scaled = scaling.float() * src.float()\n    else:\n        scaling = None\n        ref_src_scaled = src.float()\n    ref_out = torch.index_add(\n        inp.float(), dim=0, source=ref_src_scaled, index=index, alpha=alpha\n    ).to(dtype)\n    grad_output = torch.randn_like(ref_out)\n    ref_out.backward(grad_output)\n    ref_grads = {k: v.grad for k, v in TENSORS.items()}\n    for v in TENSORS.values():\n        v.grad = None\n\n    # Test FW\n    out = xops.scaled_index_add(\n        inp.clone(),\n        index,\n        src,\n        scaling,\n        alpha,\n    )\n    assert_allclose(out, ref_out, \"fw\", atol=4e-3, rtol=1e-3)\n    # Test BW\n    out.backward(grad_output)\n    for k, v in TENSORS.items():\n        atol = 1e-5\n        rtol = 1e-5\n        # NOTE: Ordering of operations is not 100% the same as PT, hence the small numeric diff\n        if k == \"scaling\":\n            atol, rtol = 5e-2, 1e-2\n        assert_allclose(v.grad, ref_grads[k], f\"{k}.grad\", atol=atol, rtol=rtol)  # type: ignore\n\n\n@pytest.mark.skipif(not indexing.IndexSelect.is_available(), reason=\"not available\")\n@pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\n@pytest.mark.parametrize(\"D\", [1536])\n@pytest.mark.parametrize(\"batches\", [((48, 25), (192, 50))])\ndef test_index_select_cat(D, batches) -> None:\n    torch.manual_seed(0)\n    dtype = torch.float16\n\n    num_rows = 0\n    for B, seqlen in batches:\n        num_rows += B * seqlen\n\n    src = torch.randn([num_rows, D], device=\"cuda\", dtype=dtype, requires_grad=True)\n    indices = []\n    sources = []\n    rows_begin = 0\n    for B, seqlen in batches:\n        index = [i for i in range(B)]\n        random.Random(B).shuffle(index)\n        indices.append(\n            torch.tensor(index[: int(0.6 * B)], dtype=torch.int64, device=\"cuda\")\n        )\n        sources.append(\n            src[rows_begin : rows_begin + B * seqlen].reshape([B, seqlen * D])\n        )\n        rows_begin += B * seqlen\n\n    # PT implem\n    ref_out = torch.cat([s[i].flatten() for s, i in zip(sources, indices)], dim=0)\n    gradient_out = torch.randn_like(ref_out)\n    ref_out.backward(gradient_out)\n    assert src.grad is not None\n    ref_grad = src.grad.clone()\n    src.grad = None\n\n    # xFormers implem\n    out = xops.index_select_cat(sources, indices)\n    assert_allclose(out, ref_out, \"fw\")\n    out.backward(gradient_out)\n    assert src.grad is not None\n    assert_allclose(src.grad, ref_grad, \"src.grad\")\n"
  },
  {
    "path": "tests/test_mem_eff_attention.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport logging\nimport math\nimport random\nfrom contextlib import nullcontext\nfrom typing import Any, List, Optional, Sequence, Tuple, Type, TypeVar\n\nimport pytest\nimport torch\n\ntry:\n    from mtia.re.re_unittest_lib import init_mtia_device\n\n    init_mtia_device()\nexcept ImportError:\n    # Failed to load MTIA libraries, so just keep going without MTIA devices\n    pass\n\nimport xformers.ops\nfrom scipy.stats import binomtest\nfrom torch.utils.checkpoint import checkpoint\nfrom xformers.attn_bias_utils import create_attn_bias, pack_kv_cache\nfrom xformers.ops import fmha\nfrom xformers.ops.fmha import ALL_BW_OPS, ALL_FW_OPS\nfrom xformers.ops.fmha.common import (\n    AttentionFwOpBase,\n    AttentionOpBase,\n    pack_fp8_tensorwise_per_head,\n)\nfrom xformers.ops.fmha.dispatch import _dispatch_fw_priority_list\n\nfrom .utils import (\n    assert_allclose,\n    construct_fp8_attention_inputs,\n    cuda_only,\n    cuda_or_mtia_only,\n    disable_on_mtia,\n    disable_on_rocm,\n    disable_tf32,\n    ref_attention_bmhk_for_test,\n    ref_attention_for_test,\n    rocm_only,\n    use_cpu_ref,\n)\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\nsm70_or_better_only = pytest.mark.skipif(\n    torch.version.cuda is not None and compute_capability < (7, 0),\n    reason=\"requires sm70+\",\n)\nsm75_or_better_only = pytest.mark.skipif(\n    torch.version.cuda is not None and compute_capability < (7, 5),\n    reason=\"requires sm75+\",\n)\nsm80_or_better_only = pytest.mark.skipif(\n    torch.version.cuda is not None and compute_capability < (8, 0),\n    reason=\"requires sm80+\",\n)\nsm90_or_better_only = pytest.mark.skipif(\n    compute_capability < (9, 0),\n    reason=\"requires sm90+\",\n)\nsm100_or_better_only = pytest.mark.skipif(\n    compute_capability < (10, 0), reason=\"requires sm100+\"\n)\nskip_if_sm100_or_better = pytest.mark.skipif(\n    compute_capability >= (10, 0), reason=\"not supported on Blackwell\"\n)\n\nskip_if_rocm = pytest.mark.skipif(\n    torch.version.hip is not None, reason=\"not supported on ROCm\"\n)\n_devices = [\"cpu\"]\n_devices += [\"cuda\"] if torch.cuda.is_available() else []\n\ntry:\n    import mtia.host_runtime.torch_mtia.dynamic_library  # noqa\n\n    # torch.mtia.is_available() will not work here, since test collection can be done\n    # on a machine without MTIA devices\n    _devices.append(\"mtia\")\nexcept (ImportError, OSError):\n    # Failed to load MTIA libraries, so just keep going without MTIA devices\n    pass\n\nT = TypeVar(\n    \"T\", Type[fmha.common.AttentionFwOpBase], Type[fmha.common.AttentionBwOpBase]\n)\n\nlogger = logging.getLogger(\"xformers\")\n\n\ndef _filter_unsupported_ops(ops: Sequence[T]) -> List[T]:\n    return [\n        op\n        for op in ops\n        if (\n            \"cpu\" in op.SUPPORTED_DEVICES\n            or \"mtia\" in op.SUPPORTED_DEVICES\n            or (\n                op.CUDA_MINIMUM_COMPUTE_CAPABILITY <= compute_capability\n                and (\n                    op.CUDA_MAXIMUM_COMPUTE_CAPABILITY is None\n                    or op.CUDA_MAXIMUM_COMPUTE_CAPABILITY >= compute_capability\n                )\n            )\n        )\n        and op.is_available()\n    ]\n\n\nALL_FW_OPS = _filter_unsupported_ops(ALL_FW_OPS)\nALL_BW_OPS = _filter_unsupported_ops(ALL_BW_OPS)\n\n\ndef sample_random_supported_fw(\n    inp: fmha.Inputs, seed, op_bw: Type[fmha.common.AttentionBwOpBase]\n) -> Type[fmha.common.AttentionFwOpBase]:\n    r = random.Random(seed)\n    fw_ops = list(ALL_FW_OPS)\n    if op_bw == fmha.cutlass_blackwell.BwOp:\n        fw_ops = [fmha.cutlass_blackwell.FwOp, fmha.flash.FwOp]\n    if (\n        isinstance(inp.attn_bias, fmha.attn_bias.VARLEN_BIASES)\n        and inp.attn_bias.q_seqinfo.seqstart.shape[0] > 2\n    ):\n        fw_ops = [\n            op for op in fw_ops if op.VARLEN_LSE_PACKED == op_bw.VARLEN_LSE_PACKED\n        ]\n    r.shuffle(fw_ops)\n    for op in fw_ops:\n        if op.supports(inp):\n            return op\n    raise NotImplementedError(f\"Could not find a FW operator for: {inp}\")\n\n\ndef generate_test_shapes_B_Mq_Mkv_H_K_Kv(op):\n    shapes = []\n    for B in op._TEST_BATCH_SIZES:\n        for Mq in [32, 256]:\n            for Mkv in [32, 64, 256, 1024]:\n                for K in op._TEST_K:\n                    shapes.append((B, Mq, Mkv, 1, K, K))\n        Mq = 256\n        Mkv = 128\n        K = 32\n        H = 1\n        # Weird values of parameters\n        for M in [2, 3, 15, 31, 32, 34, 68, 72, 90, 132, 136]:\n            shapes.append((B, M, Mkv, H, K, K))\n            shapes.append((B, Mq, M, H, K, K))\n        Ks = [1, 2, 3, 31, 34, 36, 38, 40, 64, 80, 160, 256 + 2, 256 + 8, 512]\n        for _K in Ks:\n            if op.SUPPORTED_MIN_K <= _K <= op.SUPPORTED_MAX_K:\n                shapes.append((B, Mq, Mkv, H, _K, _K))\n        # Different value for K / Kv\n        if op.SUPPORTS_DIFFERENT_VALUE_EMBED:\n            for _K in [32, 36, 64, 256 + 8]:\n                shapes.append((B, Mq, Mkv, H, K, _K))\n                shapes.append((B, Mq, Mkv, H, _K, K))\n        # Exotic sizes\n        for K in op._TEST_K:\n            shapes.append((B, 16, 1024, H, K, K))\n            shapes.append((B, 1024, 16, H, K, K))\n        # Some number of heads\n        for H in [3, 5, 12]:\n            shapes.append((max(1, B // H), Mq, Mkv, H, K, K))\n    # Filter-out not supported shapes\n    shapes = [\n        shape\n        for shape in shapes\n        if len(\n            op.shape_not_supported_reasons(\n                Mq=shape[1], Mkv=shape[2], K=shape[4], Kv=shape[5]\n            )\n        )\n        == 0\n    ]\n    # Add some random shapes\n    if op in [\n        fmha.cutlass.FwOp,\n        fmha.cutlass.BwOp,\n        fmha.cutlass_blackwell.FwOp,\n        fmha.cutlass_blackwell.BwOp,\n        fmha.flash.BwOp,\n        fmha.ck.FwOp,\n    ]:\n        K_CHOICES = [8 * i for i in range(1, 256 // 8)]\n        r = random.Random(0)\n        found_count = 0\n        while found_count < 200:\n            B = r.randint(1, 400)\n            Mq = r.randint(1, 500)\n            Mkv = r.randint(1, 500)\n            H = r.randint(2, 11)\n            B = max(B // H, 1)\n            K = r.choice(K_CHOICES)\n            Kv = r.choice(K_CHOICES)\n            if not op.SUPPORTS_DIFFERENT_VALUE_EMBED:\n                Kv = K\n            if len(op.shape_not_supported_reasons(Mq, Mkv, K, Kv)):\n                continue\n            found_count += 1\n            shapes.append((B, Mq, Mkv, H, K, Kv))\n    return shapes\n\n\ndef make_id(op, device, dtype, bias_type, *shape):\n    return (\n        f\"{op.NAME}-{device}-{str(dtype)}-{bias_type.__name__}\"\n        f\"-{'-'.join([str(s) for s in shape])}\"\n    )\n\n\n# This temporary working is necessary because the MTIA test collection might not happen\n# on the same device as the device the tests are actually executed on. If test collection\n# is done on a device without MTIA, the supported masks will contain masks that MTIA support\n# and the corresponding tests will get collected. But when it comes time to actually run the\n# tests, the mask won't be supported because it is run on an actual MTIA device.\ndef get_supported_attn_bias_types(op):\n    supported_attn_bias_types = op.SUPPORTED_ATTN_BIAS_TYPES\n\n    try:\n        import mtia.host_runtime.torch_mtia.dynamic_library  # noqa\n\n        supported_attn_bias_types = [\n            b\n            for b in supported_attn_bias_types\n            if not issubclass(\n                b,\n                (\n                    fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n                    fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n                ),\n            )\n        ]\n    except (ImportError, OSError):\n        pass\n\n    return supported_attn_bias_types\n\n\ndef _generate_op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv(\n    ops_list: Sequence[Type[fmha.AttentionOpBase]], max_shapes_per_op: int = 65000\n):\n    r = random.Random(0)\n    combination = []\n    for op in ops_list:\n        op_count = 0\n        # Sort list of masks, so it's deterministic across runs\n        LIST_MASKS = sorted(get_supported_attn_bias_types(op), key=str)\n        for shape in generate_test_shapes_B_Mq_Mkv_H_K_Kv(op):\n            has_one = False\n            for device in _devices:\n                if device not in op.SUPPORTED_DEVICES:\n                    continue\n                # Sort set of dtypes to make it deterministic across runs\n                for dtype in sorted(op.SUPPORTED_DTYPES, key=str):\n                    # \"normal_kernel_cuda\" not implemented for 'Float8_e4m3fn'\n                    if dtype in [torch.float8_e4m3fn]:\n                        continue\n                    bias_type = r.choice(LIST_MASKS)\n                    # Avoid using too much memory\n                    B, Mq, Mkv, H, K, Kv = shape\n                    if bias_type not in [\n                        type(None),\n                        fmha.attn_bias.LowerTriangularMask,\n                    ]:\n                        B = min(B, 12)\n\n                        if bias_type in {\n                            fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask,\n                            fmha.attn_bias.BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n                        }:\n                            Mq, Mkv = min(Mkv, Mq), max(Mkv, Mq) + 2\n                        elif bias_type in {\n                            fmha.attn_bias.BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n                            fmha.attn_bias.BlockDiagonalLocalAttentionPaddedKeysMask,\n                            fmha.attn_bias.BlockDiagonalCausalWithOffsetGappyKeysMask,\n                            fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n                            fmha.attn_bias.BlockDiagonalPaddedKeysMask,\n                            fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n                            fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n                            fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n                            fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n                        }:\n                            Mq, Mkv = min(Mkv, Mq), max(Mkv, Mq)\n                    new_shape = (B, Mq, Mkv, H, K, Kv)\n                    combination.append((op, device, dtype, bias_type, *new_shape))\n                    has_one = True\n            if has_one:\n                op_count += 1\n            if op_count > max_shapes_per_op:\n                break\n        # Some specific shapes for which we want to run without any mask\n        bias_type = type(None)\n        for shape in (\n            # Some strides/dims don't fit on an uint16\n            (1, 128, 128, 300, 128, 128),\n            (13, 1, 67, 200, 8, 8),\n            (1, 1 + 2**16, 4, 1, 8, 8),\n            (1, 4, 1 + 2**16, 1, 8, 8),\n            # TODO: Some strides don't fit on an uint32\n            # Crashes on Flash, Errors on Cutlass\n            # (1, 1, 64000, 300, 128, 128)\n        ):\n            for device in _devices:\n                if device not in op.SUPPORTED_DEVICES:\n                    continue\n                # Sort set of dtypes to make it deterministic across runs\n                for dtype in sorted(op.SUPPORTED_DTYPES, key=str):\n                    # \"normal_kernel_cuda\" not implemented for 'Float8_e4m3fn'\n                    if dtype in [torch.float8_e4m3fn]:\n                        continue\n                    combination.append((op, device, dtype, bias_type, *shape))\n    return {\n        \"argvalues\": combination,\n        \"ids\": [make_id(*c) for c in combination],\n    }\n\n\nparametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv = pytest.mark.parametrize(\n    \"opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\",\n    **_generate_op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv(ALL_FW_OPS),\n)\nparametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs = pytest.mark.parametrize(\n    \"opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\",\n    **_generate_op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv(ALL_FW_OPS, max_shapes_per_op=1),\n)\nparametrize_opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv = pytest.mark.parametrize(\n    \"opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\",\n    **_generate_op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv(ALL_BW_OPS),\n)\nparametrize_opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs = pytest.mark.parametrize(\n    \"opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\",\n    **_generate_op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv(ALL_BW_OPS, max_shapes_per_op=1),\n)\n\n\ndef _rand_partition(r: random.Random, total: int, n: int) -> List[int]:\n    # returns list of n nonnegative integers summing to total\n    idx = {0, total}\n    while len(idx) < n + 1:\n        idx.add(r.randint(1, total - 1))\n    s = sorted(idx)\n    return [e - b for b, e in zip(s[:-1], s[1:])]\n\n\ndef get_bias_grad(attn_bias, clear: bool = False) -> Optional[torch.Tensor]:\n    tensor_with_grad: Optional[torch.Tensor] = None\n    if isinstance(attn_bias, torch.Tensor):\n        tensor_with_grad = attn_bias\n    if tensor_with_grad is not None:\n        grad = tensor_with_grad.grad\n        if clear:\n            tensor_with_grad.grad = None\n        return grad\n    return None\n\n\ndef create_tensors(\n    op: Optional[Type[AttentionOpBase]],\n    device,\n    dtype,\n    attn_bias_type,\n    B,\n    q_len,\n    kv_len,\n    h,\n    k,\n    kv,\n    *,\n    attn_bias_requires_grad: bool = False,\n    fmt: str = \"BMK\",\n    g: int = 1,\n) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, Any]:\n    torch.manual_seed(B * q_len + kv_len * k + kv)\n\n    mask_is_bottom_right = attn_bias_type is not None and issubclass(\n        attn_bias_type,\n        (\n            fmha.attn_bias.LowerTriangularFromBottomRightMask,\n            fmha.attn_bias.LowerTriangularFromBottomRightLocalAttentionMask,\n            fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask,\n            fmha.attn_bias.BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            fmha.attn_bias.BlockDiagonalCausalLocalAttentionMask,\n            fmha.attn_bias.LocalAttentionFromBottomRightMask,\n        ),\n    )\n    if mask_is_bottom_right and q_len > kv_len:\n        # Bottom-right attention and local-attention masks require q_len <= kv_len\n        kv_len = q_len\n\n    if attn_bias_type is not None and issubclass(\n        attn_bias_type,\n        (\n            fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n            fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n        ),\n    ):\n        page_size_choices = [256, 512]\n        if op is not None and issubclass(op, fmha.triton_splitk.FwOp):\n            # TODO: enable small pages for flash attention when that's implemented\n            page_size_choices.extend([64, 128])\n        page_size = random.choice(page_size_choices)\n        kv_len_paged = (kv_len + page_size - 1) // page_size * page_size\n    else:\n        kv_len_paged = kv_len\n        page_size = None\n\n    scale = 3\n    if fmt == \"BMK\":\n        query = torch.randn((B * h, q_len, k), device=device, dtype=dtype)\n        key = torch.randn((B * h, kv_len_paged, k), device=device, dtype=dtype)\n        value = torch.randn((B * h, kv_len_paged, kv), device=device, dtype=dtype)\n    elif fmt == \"BMHK\":\n        query = torch.randn((B, q_len, h, k), device=device, dtype=dtype)\n        key = torch.randn((B, kv_len_paged, h, k), device=device, dtype=dtype)\n        value = torch.randn((B, kv_len_paged, h, kv), device=device, dtype=dtype)\n    else:\n        assert fmt == \"BMGHK\"\n        query = torch.randn((B, q_len, g, h, k), device=device, dtype=dtype)\n        key = torch.randn((B, kv_len_paged, g, 1, k), device=device, dtype=dtype)\n        value = torch.randn((B, kv_len_paged, g, 1, kv), device=device, dtype=dtype)\n\n    for x in [query, key, value]:\n        x.mul_(scale)\n\n    if fmt == \"BMGHK\":\n        # Expand - after the in-place mul\n        key = key.expand((B, kv_len_paged, g, h, k))\n        value = value.expand((B, kv_len_paged, g, h, k))\n\n    if fmt == \"BMK\" and not fmha.common._is_bias_type_supported_in_BMK(attn_bias_type):\n        attn_bias_type = None\n    attn_bias = None\n    if attn_bias_type is not None:\n        attn_bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=h,\n            num_heads_groups=g,\n            q_len=q_len,\n            kv_len=kv_len,\n            dtype=dtype,\n            device=device,\n            requires_grad=attn_bias_requires_grad,\n            fmt=fmt,\n            op=op,\n            page_size=page_size,\n        )\n        if isinstance(\n            attn_bias,\n            (\n                fmha.attn_bias.BlockDiagonalMask,\n                fmha.attn_bias.BlockDiagonalGappyKeysMask,\n                fmha.attn_bias.BlockDiagonalPaddedKeysMask,\n                fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n                fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n            ),\n        ):\n            query, key, value = [\n                x.reshape([1, -1, *x.shape[2:]]) for x in [query, key, value]\n            ]\n\n    inputs = fmha.Inputs(query=query, key=key, value=value, attn_bias=attn_bias)\n    if op is not None:\n        reasons = op.not_supported_reasons(inputs)\n        if reasons:\n            err_msg = f\"{op.NAME}: unsupported ({'/'.join(reasons)})\"\n            # Ensure we free memory to avoid OOMs\n            del query, key, value, attn_bias, inputs\n            pytest.skip(err_msg)\n    return query, key, value, attn_bias\n\n\ndef bmhk2bmk(tensor) -> torch.Tensor:\n    return (\n        tensor.permute((0, 2, 1, 3))\n        .contiguous()\n        .view([tensor.shape[0] * tensor.shape[2], tensor.shape[1], tensor.shape[3]])\n    )\n\n\ndef bmk2bmhk(tensor, num_heads: int) -> torch.Tensor:\n    return tensor.reshape([-1, num_heads, tensor.shape[1], tensor.shape[2]]).permute(\n        (0, 2, 1, 3)\n    )\n\n\ndef nanify_oob_seqlen(x: torch.Tensor) -> torch.Tensor:\n    align_to = 256\n    if x.shape[1] % align_to == 0:\n        return x\n    pad = [0, 0] * x.ndim\n    pad[-3] = align_to - (x.shape[1] % align_to)\n    x_pad = torch.nn.functional.pad(x, pad, value=math.nan)\n    return x_pad[:, : x.shape[1]]\n\n\n@pytest.mark.parametrize(\"fmt\", [\"BMK\", \"BMHK\"])\n@pytest.mark.parametrize(\"packed\", [False, True])\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\ndef test_forward(opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, packed, fmt, **kwargs):\n    (\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ) = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n    if packed and issubclass(\n        bias_type,\n        (\n            fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n            fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n        ),\n    ):\n        pytest.skip(\n            \"packed doesn't make sense with paged attention, since q has different shape than k/v\"\n        )\n    if packed and not (k == kv and q_len == kv_len):\n        pytest.skip(\n            f\"packed incompatible with `k ({k}) != kv ({kv})` or `q_len ({q_len}) != kv_len ({kv_len})`\"\n        )\n    if fmt == \"BMK\" and not fmha.common._is_bias_type_supported_in_BMK(bias_type):\n        pytest.skip(\"BMK incompatible with this bias\")\n\n    if op is fmha.ck.FwOp:\n        if (k > 256 or kv > 256) and issubclass(\n            bias_type,\n            (\n                fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n                fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n            ),\n        ):\n            pytest.skip(\"ck.FwOp hdim-512 is not supported when Paged-KVCache is used!\")\n\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=\"BMHK\" if packed else fmt,\n        **kwargs,\n    )\n    if attn_bias is not None:\n        assert type(attn_bias.to(query.device)) is type(attn_bias)\n\n    if packed:\n        c = torch.stack([query, key, value], 2)\n        if fmt == \"BMK\":\n            # bm3hk -> 3bhmk -> 3Bmk\n            c = c.permute(2, 0, 3, 1, 4).view([3, -1, q_len, k])\n            query, key, value = c[0], c[1], c[2]\n            # Re-create bias in the right format\n            attn_bias = create_attn_bias(\n                bias_type=bias_type,\n                batch_size=batch_size,\n                num_heads=h,\n                num_heads_groups=1,\n                q_len=q_len,\n                kv_len=kv_len,\n                device=device,\n                dtype=dtype,\n                requires_grad=False,\n                fmt=fmt,\n                op=op,\n            )\n        elif fmt == \"BMHK\":\n            # bm3hk -> 3 x bmhk\n            query, key, value = xformers.ops.unbind(c, 2)\n        else:\n            assert False, f\"Unsupport fmt {fmt} with packing\"\n        assert not query.is_contiguous()\n\n    out = xformers.ops.memory_efficient_attention_forward(\n        query, key, value, attn_bias, op=op\n    )\n    assert not out.isnan().any(), (\"Output has NaNs\", attn_bias)\n    out2 = xformers.ops.memory_efficient_attention_forward(\n        nanify_oob_seqlen(query),\n        nanify_oob_seqlen(key),\n        nanify_oob_seqlen(value),\n        attn_bias,\n        op=op,\n    )\n    assert not out2.isnan().any(), \"Output has NaNs - most likely reading out-of-bounds\"\n    assert torch.allclose(out, out2, atol=0.0, rtol=0.0), (\n        \"Non-deterministic behavior\",\n        attn_bias,\n    )\n\n    ref = ref_attention_for_test(query, key, value, attn_bias)\n    assert out.shape == ref.shape, out.shape\n    assert_allclose(\n        out.float(),\n        ref,\n        atol=op.ERROR_ATOL[dtype],\n        rtol=op.ERROR_RTOL.get(dtype, 1e-5),\n    )\n\n\ndef _block_diag_reshape_lse(\n    lse: torch.Tensor, q_seqinfo: fmha.attn_bias._SeqLenInfo\n) -> torch.Tensor:\n    \"\"\"LSE can be padded, let's remove the padding\"\"\"\n    parts = []\n    for slice, (start, end) in zip(lse.unbind(0), q_seqinfo.intervals()):\n        parts.append(slice[:, : end - start])\n    return torch.cat(parts, dim=1).unsqueeze(0)\n\n\n@disable_tf32\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\ndef test_logsumexp(opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv):\n    (\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ) = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n\n    if op is fmha.ck.FwOp:\n        if issubclass(\n            bias_type,\n            (\n                fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n                fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n            ),\n        ):\n            pytest.skip(\n                \"With ck.FwOp Paged-KVCache has some problem with forward training!\"\n            )\n\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=\"BMHK\",\n    )\n\n    _out, lse = xformers.ops.memory_efficient_attention_forward_requires_grad(\n        query,\n        key,\n        value,\n        op=op,\n        attn_bias=attn_bias,\n    )\n    query = query.transpose(1, 2)\n    key = key.transpose(1, 2)\n    attn = (query.float() / k**0.5) @ key.float().transpose(-2, -1)\n    if attn_bias is not None:\n        if isinstance(\n            attn_bias,\n            fmha.attn_bias.AttentionBias,\n        ):\n            bias_shape = (1, 1, query.shape[2], key.shape[2])\n            tensor_bias = attn_bias.materialize(\n                bias_shape,\n                device=query.device,\n                dtype=torch.float32,\n            )\n        else:\n            assert type(attn_bias) is torch.Tensor\n            tensor_bias = attn_bias\n        attn = attn + tensor_bias.float()\n    ref_lse = attn.logsumexp(-1)\n    if isinstance(attn_bias, fmha.attn_bias.VARLEN_BIASES):\n        # Convert to packed format if not already the case\n        if not op.VARLEN_LSE_PACKED:\n            lse = _block_diag_reshape_lse(lse, attn_bias.q_seqinfo)\n    if op is fmha.cutlass.FwOp:\n        # CUTLASS kernel pads the last dimention of LSE to 32\n        lse = lse[:, :, : ref_lse.shape[2]]\n    if op is fmha.ck.FwOp:\n        # relax numerical tolerance for CK FwOp\n        assert_allclose(lse, ref_lse, atol=2e-4, rtol=2e-4)\n    else:\n        assert_allclose(lse, ref_lse, atol=2e-4)\n\n\n@cuda_or_mtia_only\n@pytest.mark.parametrize(\n    \"op\",\n    _filter_unsupported_ops(\n        [\n            fmha.cutlass.FwOp,\n            fmha.cutlass_blackwell.FwOp,\n            fmha.flash.FwOp,\n        ]\n    ),\n)\ndef test_logsumexp_mqa(op):\n    device = torch._C._get_accelerator().type\n\n    if not op.is_available():\n        pytest.skip(\"not available\")\n\n    if device == \"mtia\" and op == fmha.cutlass.FwOp:\n        pytest.skip(\"cutlass FwOp is not supported on MTIA\")\n\n    if device == \"cuda\" and op.CUDA_MINIMUM_COMPUTE_CAPABILITY > compute_capability:\n        skip_reason = (\n            f\"requires device with capability >= {op.CUDA_MINIMUM_COMPUTE_CAPABILITY} \"\n            f\"but your GPU has capability {compute_capability} (too old)\"\n        )\n        pytest.skip(skip_reason)\n\n    dtype = torch.float16\n    s = 3\n    query = torch.randn([1, 1, 32, 128], dtype=dtype, device=device) * s\n    key = (torch.randn([1, 16, 1, 128], dtype=dtype, device=device) * s).expand(\n        -1, -1, 32, -1\n    )\n    value = (torch.randn([1, 16, 1, 128], dtype=dtype, device=device) * s).expand(\n        -1, -1, 32, -1\n    )\n    assert key.stride(2) == 0\n\n    _, lse = xformers.ops.memory_efficient_attention_forward_requires_grad(\n        query,\n        key,\n        value,\n        op=op,\n    )\n    query, key, value = [x[0].transpose(0, 1) for x in [query, key, value]]\n    attn = (query.float() / query.shape[-1] ** 0.5) @ key.float().transpose(-2, -1)\n    ref_lse = attn.logsumexp(-1)\n    assert_allclose(lse[0, :, 0], ref_lse[:, 0], atol=2e-4)\n\n\n@disable_tf32\n@pytest.mark.parametrize(\"fmt\", [\"BMK\", \"BMHK\"])\n@pytest.mark.parametrize(\"grad_out_contiguous\", [False, True])\n@parametrize_opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\ndef test_backward(\n    opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n    grad_out_contiguous,\n    fmt,\n):\n    (\n        op_bw,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ) = opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n\n    # Big batch sizes can be slow on MTIA, especially older devices because\n    # it doesn't have attention fast paths. Testing on very big batch sizes\n    # doesn't meaningfully increase our test coverage here, as long as most\n    # permutations of parameters are tested on lower batch sizes.\n    if device.startswith(\"mtia\") and batch_size >= 11:\n        pytest.skip(\"Skipping big batch test cases on MTIA\")\n\n    attn_bias_requires_grad = (\n        random.Random(q_len + kv_len * batch_size).randint(0, 1) > 0\n    )\n    query, key, value, attn_bias = create_tensors(\n        *opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        attn_bias_requires_grad=attn_bias_requires_grad,\n        fmt=fmt,\n    )\n\n    # To understand why we do this, check the comment on the\n    # `AttentionBwOpBase` class\n    scale = None\n    if op_bw.SUPPORTS_CUSTOM_SCALE and query.shape[-1] < 32:\n        scale = (1 / 32) ** 0.5\n    op_fw = (\n        sample_random_supported_fw(\n            fmha.Inputs(query=query, key=key, value=value, attn_bias=attn_bias),\n            seed=q_len * kv + kv_len * k,\n            op_bw=op_bw,\n        )\n        if op_bw != fmha.cutlass.BwOp\n        else fmha.cutlass.FwOp\n    )\n\n    if op_bw == fmha.ck.BwOp:\n        op_fw = fmha.ck.FwOp\n        if dtype == torch.bfloat16:\n            # bfloat16 testing can be enabled by export ENABLE_HIP_FMHA_RTN_BF16_CONVERT=1 when\n            # building xformers and get accurate results\n            pytest.skip(\n                \"CK Fmha backward for bfloat16 currently is not very accurate for some cases!\"\n            )\n        if not grad_out_contiguous:\n            pytest.skip(\"CK Fmha does not support non-contiguous layout for grad_out!\")\n        if k % 2 != 0:\n            pytest.skip(\n                \"CK Fmha currently requires the headdim size of query input be an even value!\"\n            )\n\n    qkv = None\n\n    if (\n        fmt == \"BMHK\"\n        and query.shape[3] == value.shape[3]\n        and query.shape[1] == value.shape[1]\n    ):\n        qkv = torch.stack([query, key, value], 2)\n        qkv.requires_grad_(True)\n        # bm3hk -> 3 x bmhk\n        query, key, value = xformers.ops.unbind(qkv, 2)\n        assert not query.is_contiguous()\n\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n\n    if not op_bw.supports(fmha.Inputs(query, key, value, attn_bias)):\n        pytest.skip(\"inputs not supported\")\n\n    out = xformers.ops.memory_efficient_attention(\n        query, key, value, attn_bias, scale=scale, op=(op_fw, op_bw)\n    )\n\n    grad_out = torch.randn_like(out)\n    if grad_out_contiguous is False:\n        grad_out = torch.tensor([1.0], dtype=query.dtype, device=device)[\n            None, None, :\n        ].expand_as(out)\n\n    out.backward(grad_out)\n\n    if qkv is None and op_bw == fmha.cutlass.BwOp:\n        assert query.stride() == query.grad.stride()\n\n    grads = []\n    if qkv is None:\n        grads = [query.grad, key.grad, value.grad]\n        query.grad = None\n        key.grad = None\n        value.grad = None\n    else:\n        grads = [qkv.grad]\n        qkv.grad = None\n    if attn_bias_requires_grad:\n        attn_bias_grad = get_bias_grad(attn_bias, clear=True)\n        if attn_bias_grad is not None:\n            grads.append(attn_bias_grad)\n\n    if use_cpu_ref(device):\n        query = query.detach().cpu()\n        key = key.detach().cpu()\n        value = value.detach().cpu()\n        grad_out = grad_out.detach().cpu()\n\n        if qkv is not None:\n            qkv = torch.stack([query, key, value], 2)\n            qkv.requires_grad_(True)\n            query, key, value = xformers.ops.unbind(qkv, 2)\n\n        query.requires_grad_(True)\n        key.requires_grad_(True)\n        value.requires_grad_(True)\n        grad_out.requires_grad_(True)\n\n        if isinstance(attn_bias, torch.Tensor):\n            attn_bias = attn_bias.cpu()\n\n    ref = ref_attention_for_test(query, key, value, attn_bias, scale=scale)\n    ref.backward(grad_out)\n\n    assert_allclose(\n        out.float().to(ref.device),\n        ref.float(),\n        \"fw pass\",\n        atol=op_fw.ERROR_ATOL[dtype],\n        rtol=op_fw.ERROR_RTOL[dtype],\n    )\n\n    del out\n    del grad_out\n    del ref\n\n    atol = op_bw.ERROR_ATOL[dtype]\n    rtol = op_bw.ERROR_RTOL[dtype]\n\n    # This is a special case without masks where the accumulated numbers become so big that\n    # we lose too much precision, especially on bfloat16. For this reason, the default bfloat16\n    # tolerance for the backward pass is set to 0.9, but in some cases on MTIA we get up to 1.3,\n    # probably due to the fact that the implementation doesn't use the fused kernels yet, which\n    # increases the precision loss caused by the accumulation.\n    if (\n        device.startswith(\"mtia\")\n        and issubclass(bias_type, type(None))\n        and q_len >= 2**16\n    ):\n        atol *= 1.6\n\n    grads_ref = []\n    grads_name = []\n    if qkv is None:\n        assert isinstance(query.grad, torch.Tensor)\n        assert isinstance(key.grad, torch.Tensor)\n        assert isinstance(value.grad, torch.Tensor)\n        grads_ref = [query.grad, key.grad, value.grad]\n        grads_name = [\"query\", \"key\", \"value\"]\n    else:\n        assert isinstance(qkv.grad, torch.Tensor)\n        grads_ref = [qkv.grad]\n        grads_name = [\"qkv\"]\n\n    if attn_bias_requires_grad:\n        attn_bias_grad = get_bias_grad(attn_bias)\n        if attn_bias_grad is not None:\n            grads_ref.append(attn_bias.grad)\n            grads_name.append(\"bias\")\n\n    del query\n    del key\n    del value\n    del qkv\n\n    assert len(grads_ref) == len(\n        grads\n    ), \"Wrong number of gradients (maybe bias grad didn't backprop?)\"\n    for name, calc_grad, ref_grad in zip(grads_name, grads, grads_ref):\n        assert_allclose(\n            calc_grad.to(ref_grad.device),\n            ref_grad,\n            msg=f\"{op_fw.NAME}+{op_bw.NAME}:{name}\",\n            atol=atol,\n            rtol=rtol,\n        )\n\n\ndef _vec_binom_test(x, n, p):\n    \"\"\"\n    vectorized implementation of scipy.stats.binom_test\n    this makes our tests much faster\n    reference: https://github.com/scipy/scipy/blob/v1.8.0/scipy/stats/_morestats.py#L2609-L2702\n    \"\"\"\n    import numpy as np\n    from scipy.stats import distributions\n\n    x = np.atleast_1d(x)\n    d = distributions.binom.pmf(x, n, p)[:, None]\n    rerr = 1 + 1e-7\n    # x < p * n case\n    i = np.arange(np.ceil(p * n), n + 1)\n    y = np.sum(distributions.binom.pmf(i, n, p) <= d * rerr, axis=1)\n    pval1 = distributions.binom.cdf(x, n, p) + distributions.binom.sf(n - y, n, p)\n\n    # other case\n    i = np.arange(np.floor(p * n) + 1)\n    y = np.sum(distributions.binom.pmf(i, n, p) <= d * rerr, axis=1)\n    pval2 = distributions.binom.cdf(y - 1, n, p) + distributions.binom.sf(x - 1, n, p)\n\n    pval = np.where(x < p * n, pval1, pval2)\n    pval = np.minimum(1.0, pval)\n    return pval\n\n\ndef _get_drop_mask(op, batch_size, q_len, kv_len, p, device):\n    assert op == fmha.ck.FwOp, f\"Op {op.NAME} does not expose dropout mask\"\n    mask = torch.empty((batch_size, 1, q_len, kv_len), device=device)\n    # rand_uniform is an int8_t tensor\n    rand_uniform = torch.ops.xformers._ck_rand_uniform(p, mask)\n    mask = (rand_uniform <= int((1.0 - p) * 255.0)).to(torch.float32)\n    mask = mask.reshape(batch_size, q_len, kv_len)\n\n    return mask\n\n\n@rocm_only\n@pytest.mark.parametrize(\"attn_bias\", [None, fmha.attn_bias.LowerTriangularMask()])\n@pytest.mark.parametrize(\"seed\", [42, 124])\n@pytest.mark.parametrize(\"p\", [0.3, 0.7])\n@pytest.mark.parametrize(\"k_len\", [32])\n@pytest.mark.parametrize(\"batch_size\", [1, 2])\n@pytest.mark.parametrize(\"kv_len\", [3, 15, 32, 33, 65])\n@pytest.mark.parametrize(\"q_len\", [2, 33])\ndef test_dropout_ck(q_len, kv_len, batch_size, k_len, p, seed, attn_bias):\n    op = fmha.ck.FwOp\n    device = \"cuda\"\n    scale = 3\n\n    dtype = torch.float16\n\n    query = torch.randn((batch_size, q_len, k_len), device=device, dtype=dtype) * scale\n    key = torch.randn((batch_size, kv_len, k_len), device=device, dtype=dtype) * scale\n    value = torch.randn((batch_size, kv_len, k_len), device=device, dtype=dtype) * scale\n\n    inputs_for_support_check = fmha.Inputs(query, key, value, attn_bias, p, None)\n    if not op.supports(inputs_for_support_check):\n        del query, key, value, attn_bias\n        pytest.skip(f\"{op.NAME}: unsupported input\")\n\n    torch.manual_seed(seed)\n    out = xformers.ops.memory_efficient_attention(\n        query, key, value, attn_bias, p, op=(op, None)\n    )\n\n    torch.manual_seed(seed)\n    out2 = xformers.ops.memory_efficient_attention(\n        query, key, value, attn_bias, p, op=(op, None)\n    )\n\n    assert_allclose(out, out2, \"dropout reproducibility\")\n\n    torch.manual_seed(seed)\n    mask = _get_drop_mask(op, batch_size, q_len, kv_len, p, device)\n    ref = ref_attention_for_test(query, key, value, attn_bias, mask, p)\n\n    if dtype is torch.float:\n        assert_allclose(out, ref, atol=2e-4), f\"{(out - ref).abs().max()}\"\n    else:\n        assert_allclose(out.float(), ref, atol=2.8e-2), f\"{(out - ref).abs().max()}\"\n\n    num_trials = 1000\n    p_val_tol = 1e-6\n    keep_prob = 1 - p\n    masks = []\n    for i in range(num_trials):\n        mask = _get_drop_mask(op, batch_size, q_len, kv_len, p, device)\n        masks.append(mask.clone().cpu())\n    masks = torch.stack(masks, dim=0)\n    p_value = binomtest(int(masks.sum()), masks.numel(), p=keep_prob).pvalue\n    assert p_value > p_val_tol, p_value\n    masks = masks.sum(0).flatten()\n    p_values = _vec_binom_test(masks, num_trials, p=keep_prob)\n    assert all(p_values > p_val_tol)\n\n\n@rocm_only\n@pytest.mark.parametrize(\"p\", [0.000001, 0.3, 0.7])\n@pytest.mark.parametrize(\"k\", [16, 64, 128])\n@pytest.mark.parametrize(\"batch_size\", [1, 2])\n@pytest.mark.parametrize(\"kv_len\", [3, 248, 256])\n@pytest.mark.parametrize(\"q_len\", [3, 248, 256])\ndef test_dropout_backward_ck(q_len, kv_len, batch_size, k, p):\n    op = fmha.ck.FwOp\n    dtype = torch.float16\n    if not op.is_available():\n        pytest.skip()\n\n    scale = 3\n    device = \"cuda\"\n    query = torch.randn((batch_size, q_len, k), device=device, dtype=dtype) * scale\n    key = torch.randn((batch_size, kv_len, k), device=device, dtype=dtype) * scale\n    value = torch.randn((batch_size, kv_len, k), device=device, dtype=dtype) * scale\n\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n\n    grad_out = torch.ones_like(query)\n\n    assert op.supports(fmha.Inputs(query=query, key=key, value=value, p=p))\n\n    seed = 42\n    torch.manual_seed(seed)\n    out = xformers.ops.memory_efficient_attention(query, key, value, p=p, op=(op, None))\n\n    out.backward(grad_out)\n\n    grad_q = query.grad\n    grad_k = key.grad\n    grad_v = value.grad\n\n    query.grad = None\n    key.grad = None\n    value.grad = None\n\n    torch.manual_seed(seed)\n    mask = _get_drop_mask(op, batch_size, q_len, kv_len, p, device)\n\n    ref = ref_attention_for_test(query, key, value, None, mask, p)\n    ref.backward(grad_out)\n\n    atol, rtol = (\n        fmha.AttentionBwOpBase.ERROR_ATOL[dtype],\n        fmha.AttentionBwOpBase.ERROR_RTOL[dtype],\n    )\n    assert_allclose(\n        grad_v,\n        value.grad,\n        \"grad_v\",\n        atol=atol,\n        rtol=rtol,\n    )\n    # TODO: Investigate why precision is worse\n    if dtype in [torch.float16, torch.bfloat16]:\n        atol = atol * 2 + 0.15\n        rtol = rtol * 2\n    assert_allclose(\n        grad_q,\n        query.grad,\n        \"grad_q\",\n        atol=atol,\n        rtol=rtol,\n    )\n    assert_allclose(\n        grad_k,\n        key.grad,\n        \"grad_k\",\n        atol=atol,\n        rtol=rtol,\n    )\n\n\n@pytest.mark.parametrize(\"fmt\", [\"BMK\", \"BMHK\"])\n@parametrize_opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_lowlevel_api_shapes(opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, fmt):\n    query, key, value, attn_bias = create_tensors(\n        *opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, fmt=fmt\n    )\n    grad_out = torch.ones_like(query)\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n\n    inputs = fmha.Inputs(query=query, key=key, value=value, attn_bias=attn_bias)\n    op_bw = opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv[0]\n    op_fw = sample_random_supported_fw(\n        inputs, seed=f\"{op_bw.NAME}{query.shape}\", op_bw=op_bw\n    )\n    out, lse = xformers.ops.memory_efficient_attention_forward_requires_grad(\n        query, key, value, attn_bias, op=op_fw\n    )\n    assert out.ndim == query.ndim\n    dq, dk, dv = xformers.ops.memory_efficient_attention_backward(\n        grad_out, out, lse, query, key, value, attn_bias, op=op_bw\n    )\n    assert dq.shape == query.shape\n    assert dk.shape == key.shape\n    assert dv.shape == value.shape\n\n\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_cuda_streams(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n):\n    (\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ) = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n    if device != \"cuda\":\n        pytest.skip(\"Not CUDA\")\n\n    bias_type = None\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv = [\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ]\n    s_hipri = torch.cuda.Stream(priority=-1)\n    s_lopri = torch.cuda.Stream(priority=0)\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, fmt=\"BMHK\"\n    )\n    torch.cuda.synchronize()\n    with torch.cuda.stream(s_lopri):\n        torch.cuda._sleep(100_000_000)  # wait 100m cycles\n        query *= 2\n    s_hipri.wait_stream(s_lopri)\n    with torch.cuda.stream(s_hipri):\n        # If the kernel is scheduled in the main stream\n        # `query * 2` has not been executed yet\n        out = xformers.ops.memory_efficient_attention(query, key, value, op=(op, None))\n    # Test that `s_lopri` is still sleeping\n    # and that `query *= 2` has not been executed yet\n    query2_main_stream = query * 2\n    torch.cuda.synchronize()\n    # TODO: Figure out why this is failing sometimes\n    # The sleep timer seems to be high enough already ...\n    # assert torch.allclose(query2_main_stream, query), \"Need to increase sleep time\"\n    del query2_main_stream\n\n    ref = ref_attention_for_test(query, key, value)\n    assert out.shape == ref.shape, out.shape\n\n    assert_allclose(\n        out.float(),\n        ref.float(),\n        atol=op.ERROR_ATOL[dtype],\n        rtol=op.ERROR_RTOL.get(dtype, 1e-5),\n    )\n\n\n@disable_tf32\n@parametrize_opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_custom_scale(opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv):\n    p = 0.0\n    scale = 0.1\n\n    (\n        op_bw,\n        device,\n        dtype,\n        _,\n        B,\n        q_len,\n        kv_len,\n        H,\n        k,\n        Kv,\n    ) = opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n    torch.manual_seed(q_len + kv_len + k)\n    if device not in {\"cuda\", \"mtia\"}:\n        pytest.skip(\"Not CUDA or MTIA\")\n\n    query, key, value, attn_bias = create_tensors(\n        *opBW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, fmt=\"BMK\"\n    )\n    inputs = fmha.Inputs(\n        query=query, key=key, value=value, attn_bias=attn_bias, scale=scale\n    )\n    op_fw = sample_random_supported_fw(inputs, seed=q_len * k + kv_len * k, op_bw=op_bw)\n    grad_out = query.new_ones(B * H, q_len, Kv)\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n\n    reasons = op_fw.not_supported_reasons(inputs)\n    if reasons:\n        pytest.skip(f\"{op_fw.NAME}: unsupported ({'/'.join(reasons)})\")\n    reasons = op_bw.not_supported_reasons(inputs)\n    if reasons:\n        pytest.skip(f\"{op_bw.NAME}: unsupported ({'/'.join(reasons)})\")\n\n    # NOTE: we still need to scale the inputs to not blowup\n    # the pre-softmax values (numerical stability)\n    s = k**-0.5\n    out = xformers.ops.memory_efficient_attention(\n        query * s, key, value, attn_bias, p, scale, op=(op_fw, op_bw)\n    )\n    out.backward(grad_out)\n    grad_q, grad_k, grad_v = query.grad, key.grad, value.grad\n    query.grad = key.grad = value.grad = None\n\n    ref = ref_attention_for_test(query * s, key, value, attn_bias, None, p, scale)\n    ref.backward(grad_out)\n    ref_grad_q, ref_grad_k, ref_grad_v = query.grad, key.grad, value.grad\n    query.grad = key.grad = value.grad = None\n\n    atol = op_fw.ERROR_ATOL[dtype]\n    rtol = op_fw.ERROR_RTOL[dtype]\n    assert_allclose(out.float(), ref.float(), \"out\", atol=atol, rtol=rtol)\n    atol = op_bw.ERROR_ATOL[dtype]\n    rtol = op_bw.ERROR_RTOL[dtype]\n    assert_allclose(grad_q, ref_grad_q, \"grad_q\", atol=atol, rtol=rtol)\n    assert_allclose(grad_k, ref_grad_k, \"grad_k\", atol=atol, rtol=rtol)\n    assert_allclose(grad_v, ref_grad_v, \"grad_v\", atol=atol, rtol=rtol)\n\n\ndef apply_attention(query, key, value, attn_bias, op_fw, proj):\n    x = xformers.ops.memory_efficient_attention(\n        query, key, value, attn_bias=attn_bias, op=(op_fw, None)\n    )\n    x = proj(x)\n    return x\n\n\n# TODO: Enable this for MTIA\n# MTIA doesn't support this yet\n@disable_on_mtia\n@pytest.mark.parametrize(\"use_reentrant\", [False, True])\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_grad_checkpointing(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n    use_reentrant,\n):\n    fmt = \"BMHK\"\n    (\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    ) = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\n    if op is fmha.triton_splitk.FwOp:\n        pytest.skip(\"Triton Flash Decoding doesn't support backward pass yet\")\n    if op is fmha.ck.FwOp:\n        pytest.skip(\"ck-tiled FMHA doesn't supported backward pass yet\")\n\n    bias_type = None\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv = (\n        op,\n        device,\n        dtype,\n        bias_type,\n        batch_size,\n        q_len,\n        kv_len,\n        h,\n        k,\n        kv,\n    )\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=fmt,\n    )\n    qkv = None\n\n    if (\n        fmt == \"BMHK\"\n        and query.shape[3] == value.shape[3]\n        and query.shape[1] == value.shape[1]\n    ):\n        qkv = torch.stack([query, key, value], 2)\n        qkv.requires_grad_(True)\n        # bm3hk -> 3 x bmhk\n        query, key, value = xformers.ops.unbind(qkv, 2)\n        assert not query.is_contiguous()\n\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n\n    proj = torch.nn.Linear(kv, k, device=device, dtype=dtype)\n\n    x = query\n    for _ in range(5):\n        x = checkpoint(\n            apply_attention,\n            x,\n            key,\n            value,\n            attn_bias,\n            op,\n            proj,\n            use_reentrant=use_reentrant,\n        )\n    x.mean().backward()\n\n\n@pytest.mark.parametrize(\"op\", ALL_FW_OPS, ids=[op.NAME for op in ALL_FW_OPS])\ndef test_unsupported_cpu(op: Type[fmha.AttentionFwOpBase]):\n    q = torch.empty([1, 1, 1, 32])\n    with pytest.raises(ValueError):\n        fmha.memory_efficient_attention(q, q, q, op=(op, None))\n\n\n@cuda_only\n@pytest.mark.parametrize(\"op\", ALL_FW_OPS, ids=[op.NAME for op in ALL_FW_OPS])\ndef test_unsupported_stride_lastdim(op: Type[fmha.AttentionFwOpBase]):\n    K = max(op.SUPPORTED_MIN_K, 32)\n    q = torch.empty([1, 1, K, 4], device=\"cuda\", dtype=torch.float16).permute(\n        0, 3, 1, 2\n    )\n\n    try:\n        fmha.memory_efficient_attention(q, q, q, op=(op, None))\n    except ValueError as e:\n        if \"Only work on pre-MLIR triton for now\" in str(e):\n            pytest.skip(\"Only work on pre-MLIR triton for now\")\n        q = q.contiguous()\n        fmha.memory_efficient_attention(q, q, q, op=(op, None))\n\n\n@cuda_only\n@pytest.mark.parametrize(\"op\", ALL_FW_OPS, ids=[op.NAME for op in ALL_FW_OPS])\ndef test_unsupported_stride_alignment(op: Type[fmha.AttentionFwOpBase]):\n    K = max(op.SUPPORTED_MIN_K, 32)\n    q = torch.empty([1, 2, 1, K + 1], device=\"cuda\", dtype=torch.float16)[:, :, :, :K]\n\n    try:\n        fmha.memory_efficient_attention(q, q, q, op=(op, None))\n    except ValueError as e:\n        if \"Only work on pre-MLIR triton for now\" in str(e):\n            pytest.skip(\"Only work on pre-MLIR triton for now\")\n        q = q.contiguous()\n        fmha.memory_efficient_attention(q, q, q, op=(op, None))\n\n\n@sm75_or_better_only\ndef test_unsupported_dropout_combine_flash_cutlass() -> None:\n    q = torch.empty(\n        [1, 4, 1, 16], device=\"cuda\", dtype=torch.float16, requires_grad=True\n    )\n    with pytest.raises(ValueError):\n        out = fmha.memory_efficient_attention(\n            q, q, q, p=0.1, op=(fmha.cutlass.FwOp, fmha.flash.BwOp)\n        )\n        out.backward(out)\n    with pytest.raises(ValueError):\n        out = fmha.memory_efficient_attention(\n            q, q, q, p=0.1, op=(fmha.flash.FwOp, fmha.cutlass.BwOp)\n        )\n        out.backward(out)\n\n\ndef test_attn_bias_causal() -> None:\n    m = -math.inf\n    causal_mask = torch.tensor([[0, m], [0, 0], [0, 0]])\n    tensor_bias = torch.tensor([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])\n\n    attn_bias = fmha.attn_bias.LowerTriangularMask()\n    assert_allclose(attn_bias.materialize(causal_mask.shape), causal_mask, \"causal\")\n    attn_bias = attn_bias.add_bias(tensor_bias)\n    assert_allclose(\n        attn_bias.materialize(causal_mask.shape),\n        tensor_bias + causal_mask,\n        \"causal+tensor_bias\",\n    )\n\n\ndef test_attn_bias_torch_tensor() -> None:\n    tensor_bias = torch.tensor([[1.0, 2.0, 3.0], [3.0, 4.0, 5.0]])\n    attn_bias = fmha.attn_bias.LowerTriangularMaskWithTensorBias(tensor_bias)\n    m = -math.inf\n    causal_bias = torch.tensor([[0, m, m], [0, 0, m]])\n    assert_allclose(\n        attn_bias.materialize((2, 3)), causal_bias + tensor_bias, \"tensor_bias+causal\"\n    )\n\n\ndef test_attn_bias_blockdiag() -> None:\n    queries = [\n        torch.randn([1, 3, 1, 8]),\n        torch.randn([1, 2, 1, 8]),\n        torch.randn([1, 5, 1, 8]),\n    ]\n    attn_bias, q = fmha.attn_bias.BlockDiagonalMask.from_tensor_list(queries)\n\n    # Verify mask\n    as_tensor = attn_bias.materialize((10, 10))\n    assert int((as_tensor != -math.inf).sum().item()) == 3 * 3 + 2 * 2 + 5 * 5\n    assert_allclose(as_tensor[0:3, 0:3], torch.zeros([3, 3]), \"batch0\")\n    assert_allclose(as_tensor[3:5, 3:5], torch.zeros([2, 2]), \"batch1\")\n    assert_allclose(as_tensor[5:, 5:], torch.zeros([5, 5]), \"batch2\")\n\n    # Verify we can split it back\n    queries2 = attn_bias.split(q)\n    assert len(queries) == len(queries2)\n    for q1, q2 in zip(queries, queries2):\n        assert_allclose(q1, q2)\n\n\ndef test_attn_bias_blockdiag_batched() -> None:\n    queries = [\n        torch.randn([1, 3, 1, 8]),\n        torch.randn([3, 2, 1, 8]),\n        torch.randn([1, 5, 1, 8]),\n    ]\n    attn_bias, q = fmha.attn_bias.BlockDiagonalMask.from_tensor_list(queries)\n\n    # Verify mask\n    as_tensor = attn_bias.materialize((14, 14))\n    assert int((as_tensor != -math.inf).sum().item()) == 3 * 3 + 3 * 2 * 2 + 5 * 5\n    assert_allclose(as_tensor[0:3, 0:3], torch.zeros([3, 3]), \"batch0\")\n    assert_allclose(as_tensor[3:5, 3:5], torch.zeros([2, 2]), \"batch1.0\")\n    assert_allclose(as_tensor[5:7, 5:7], torch.zeros([2, 2]), \"batch1.1\")\n    assert_allclose(as_tensor[7:9, 7:9], torch.zeros([2, 2]), \"batch1.2\")\n    assert_allclose(as_tensor[9:, 9:], torch.zeros([5, 5]), \"batch2\")\n\n    # Verify we can split it back\n    queries2 = attn_bias.split(q)\n    assert len(queries) == len(queries2)\n    for q1, q2 in zip(queries, queries2):\n        assert_allclose(q1, q2)\n\n\ndef test_attn_bias_blockdiag_crossattn_causal() -> None:\n    # Q / KV have different seqlen\n    list_q = [\n        torch.randn([1, 3, 1, 8]),\n        torch.randn([2, 1, 1, 8]),\n    ]\n    list_k = [\n        torch.randn([1, 2, 1, 8]),\n        torch.randn([2, 3, 1, 8]),\n    ]\n\n    attn_bias, q, k, _ = fmha.attn_bias.BlockDiagonalMask.from_tensor_lists_qkv(\n        list_q, list_k\n    )\n\n    # Verify mask\n    as_tensor = attn_bias.materialize((q.shape[1], k.shape[1]))\n    assert int((as_tensor != -math.inf).sum().item()) == 3 * 2 + 2 * 3 * 1\n    assert_allclose(as_tensor[0:3, 0:2], torch.zeros([3, 2]), \"batch0\")\n    assert_allclose(as_tensor[3:4, 2:5], torch.zeros([1, 3]), \"batch1.0\")\n    assert_allclose(as_tensor[4:, 5:], torch.zeros([1, 3]), \"batch1.1\")\n\n    # Also test causal version\n    as_tensor = attn_bias.make_causal().materialize((q.shape[1], k.shape[1]))\n    assert_allclose(\n        as_tensor[3:4, 2:5],\n        fmha.attn_bias.LowerTriangularMask().materialize((1, 3)),\n        \"batch1.0[causal]\",\n    )\n\n    # Verify we can split it back\n    list_q2 = attn_bias.split_queries(q)\n    assert len(list_q) == len(list_q2)\n    for q1, q2 in zip(list_q, list_q2):\n        assert_allclose(q1, q2)\n    with pytest.raises(ValueError):\n        attn_bias.split_queries(k)\n    list_k2 = attn_bias.split_kv(k)\n    assert len(list_k) == len(list_k2)\n    for k1, k2 in zip(list_k, list_k2):\n        assert_allclose(k1, k2)\n\n\ndef test_attn_bias_blockdiag_crossattn_causal_with_prefix_qk_cond() -> None:\n    list_q = [\n        torch.randn([1, 3, 1, 8]),\n    ]\n    list_k = [\n        torch.randn([1, 2, 1, 8]),\n    ]\n    attn_bias, q, k, _ = fmha.attn_bias.BlockDiagonalMask.from_tensor_lists_qkv(\n        list_q, list_k\n    )\n    with pytest.raises(ValueError):\n        attn_bias.make_causal_from_bottomright()\n\n\ndef test_attn_bias_blockdiag_crossattn_causal_with_prefix() -> None:\n    # Q / KV have different seqlen\n    list_q = [\n        torch.randn([1, 2, 1, 8]),\n        torch.randn([2, 2, 1, 8]),\n    ]\n    list_k = [\n        torch.randn([1, 2, 1, 8]),\n        torch.randn([2, 5, 1, 8]),\n    ]\n\n    attn_bias, q, k, _ = fmha.attn_bias.BlockDiagonalMask.from_tensor_lists_qkv(\n        list_q, list_k\n    )\n    as_tensor = attn_bias.make_causal_from_bottomright().materialize(\n        (q.shape[1], k.shape[1])\n    )\n    m = -math.inf\n    assert_allclose(\n        as_tensor[0:2, 0:2],\n        torch.tensor([[0, m], [0, 0]], dtype=torch.float32),\n        \"batch1.1[causal_with_prefix]\",\n    )\n    assert_allclose(\n        as_tensor[2:4, 2:7],\n        torch.tensor([[0, 0, 0, 0, m], [0, 0, 0, 0, 0]], dtype=torch.float32),\n        \"batch2.1[causal_with_prefix]\",\n    )\n    assert_allclose(\n        as_tensor[4:6, 7:12],\n        torch.tensor([[0, 0, 0, 0, m], [0, 0, 0, 0, 0]], dtype=torch.float32),\n        \"batch2.2[causal_with_prefix]\",\n    )\n\n\n@cuda_only\ndef test_attn_bias_padded() -> None:\n    bsize, n_heads, d, padding = 8, 3, 8, 32\n    torch.manual_seed(0)\n\n    # Q / KV have different seqlen\n    k = torch.randn((bsize, padding, n_heads, d), device=\"cuda\", dtype=torch.float16)\n    k_seqlen = [5, 8, 7, 1, 9, 3, 12, 32]\n    other = bsize - 1\n    v = torch.randn((bsize, padding, n_heads, d), device=\"cuda\", dtype=torch.float16)\n    n_q_first = 4\n    q = [\n        torch.randn((1, n_q_first, n_heads, d), device=\"cuda\", dtype=torch.float16),\n        torch.randn((1, other, n_heads, d), device=\"cuda\", dtype=torch.float16),\n    ]\n    q_cat = torch.cat([x.view(1, -1, n_heads, d) for x in q], dim=1)\n    q_seqlen = [n_q_first] + [1] * other\n\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=q_seqlen,\n        kv_seqlen=k_seqlen,\n        kv_padding=padding,\n    )\n\n    v = v.view(1, -1, n_heads, d)\n    k = k.view(1, -1, n_heads, d)\n\n    scores = (q_cat.transpose(1, 2) @ k.transpose(1, 2).transpose(2, 3)).float()\n    assert not scores.isnan().any()\n    mask = torch.full_like(scores, -float(\"inf\"))\n    for i, (slen, qlen) in enumerate(zip(k_seqlen, q_seqlen)):\n        kseq_start = i * padding\n        qstart = sum(q_seqlen[:i])\n        mask[:, :, qstart : qstart + qlen, kseq_start : kseq_start + slen] = torch.triu(\n            mask[:, :, qstart : qstart + qlen, kseq_start : kseq_start + slen].float(),\n            diagonal=1 + slen - qlen,\n        ).float()\n\n    scores += mask\n    assert not scores.isnan().any()\n    # 1,3,10,8 @ 1,3,8,256 -> 1,3,10,256\n    scores = torch.nn.functional.softmax(scores, -1).half()\n    # torch.Size([1, 3, 3, 32]) @ torch.Size([1, 3, 32, 8])\n    output = scores @ v.transpose(1, 2)  # 1,3,10,256 @ 1,3,256, 8 -> 1,3,10,8\n    output = output.transpose(1, 2).contiguous()\n\n    fmha_output = fmha.memory_efficient_attention_forward(\n        q_cat, k, v, attn_bias, scale=1.0\n    )\n\n    # assert torch.allclose(output, fmha_output)\n    assert_allclose(\n        output,\n        fmha_output,\n        atol=fmha.cutlass.FwOp.ERROR_ATOL[torch.float16],\n        rtol=fmha.cutlass.FwOp.ERROR_RTOL[torch.float16],\n    )\n\n\ndef _kv_heads_label(kv_heads: Optional[int]) -> str:\n    if kv_heads is None:\n        return \"\"\n    if kv_heads == 1:\n        return \"mq\"\n    return f\"gqa{kv_heads}\"\n\n\ndef _test_decoder(\n    op,\n    n_heads: int,\n    kv_heads: Optional[int],\n    padding: int,\n    bsz: int,\n    dtype: str,\n    dequant: bool = False,\n    num_queries: int = 1,\n    d: int = 128,\n) -> None:\n    if not op.is_available():\n        raise pytest.skip(\"not available\")\n    # kv_heads = 1: multiquery\n    # kv_heads = None: neither MQA nor GQA\n    # kv_heads > 1: BMGHK\n    if dtype == \"bf16\" and torch.version.cuda and compute_capability < (8, 0):\n        raise pytest.skip(\"BF16 is only supported on SM80+\")\n    import triton\n\n    if dequant and triton.__version__[:4] < \"3.0.\":\n        raise pytest.skip(\"dequant needs triton updates\")\n    dtype_ = {\"f16\": torch.float16, \"bf16\": torch.bfloat16, \"f32\": torch.float32}[dtype]\n    torch.manual_seed(1)\n    if kv_heads is not None and kv_heads > 1:\n        k_shape: Tuple[int, ...] = (1, bsz * padding, kv_heads, n_heads, d)\n        q_shape: Tuple[int, ...] = (\n            1,\n            bsz * num_queries,\n            kv_heads,\n            n_heads,\n            d,\n        )\n    else:\n        k_shape = (1, bsz * padding, n_heads, d)\n        q_shape = (1, bsz * num_queries, n_heads, d)\n\n    # TODO: support 2 kv heads etc.\n    k = torch.randn(k_shape, dtype=dtype_, device=\"cuda\")\n    k_seqlen = torch.randint(num_queries, padding + 1, (bsz,)).tolist()\n    v = torch.randn(k_shape, dtype=dtype_, device=\"cuda\")\n    q = torch.randn(q_shape, dtype=dtype_, device=\"cuda\")\n\n    if dequant:\n        k_shape = k_shape[:-1] + (d // 8 + op.NUM_GROUPS,)\n        k = torch.zeros(k_shape, dtype=torch.int32, device=\"cuda\")\n        k.random_()\n        k[..., : op.NUM_GROUPS].view(torch.float16).fill_(1.0)\n        v = torch.zeros(k_shape, dtype=torch.int32, device=\"cuda\")\n        v.random_()\n        v[..., : op.NUM_GROUPS].view(torch.float16).fill_(1.0)\n\n    if kv_heads is not None:\n        k = k[..., :1, :].expand(k_shape)\n        v = v[..., :1, :].expand(k_shape)\n\n    if skip_reasons := op.not_supported_reasons(fmha.Inputs(q, k, v)):\n        pytest.skip(\"; \".join(skip_reasons))\n\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[num_queries] * bsz,\n        kv_seqlen=k_seqlen,\n        kv_padding=padding,\n    )\n\n    decoder_output = fmha.memory_efficient_attention_forward(\n        q,\n        k,\n        v,\n        attn_bias,\n        op=op,\n    )\n\n    def dequant_cache(x):\n        x = x[..., op.NUM_GROUPS :, None].expand(k_shape[:-1] + (d // 8, 8))\n        x = x // (2 ** (4 * torch.arange(8, device=\"cuda\")))\n        x = (x % 16).flatten(start_dim=-2)\n        return x.to(dtype_) + 1.0\n\n    if dequant:\n        k = dequant_cache(k)\n        v = dequant_cache(v)\n\n    ref_output = ref_attention_for_test(q, k, v, attn_bias)\n\n    assert_allclose(\n        decoder_output.to(ref_output.dtype),\n        ref_output,\n        atol=op.ERROR_ATOL[dtype_] * 4,\n        rtol=op.ERROR_RTOL[dtype_],\n    )\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"op,dequant,dtype\",\n    [\n        (fmha.triton_splitk.FwOp_S1, False, \"bf16\"),\n        (fmha.triton_splitk.FwOp_S2, False, \"f16\"),\n        (fmha.triton_splitk.FwOp_S2, True, \"bf16\"),\n        (\n            type(\n                \"S2_8\", (fmha.triton_splitk.FwOp_S2,), {\"NUM_GROUPS\": 8, \"NAME\": \"S2_8\"}\n            ),\n            True,\n            \"bf16\",\n        ),\n    ],\n)\n@pytest.mark.parametrize(\"kv_heads\", [None, 1, 2], ids=_kv_heads_label)\n@pytest.mark.parametrize(\"n_heads\", [16])\n@pytest.mark.parametrize(\"padding, bsz\", [(32, 8), (4096, 1)])\ndef test_triton_splitk_decoder(\n    op,\n    dequant: bool,\n    kv_heads: Optional[int],\n    n_heads: int,\n    padding: int,\n    bsz: int,\n    dtype: str,\n) -> None:\n    # We omit dequant with f16: it needs a very high tol\n    _test_decoder(\n        op,\n        kv_heads=kv_heads,\n        n_heads=n_heads,\n        padding=padding,\n        bsz=bsz,\n        dtype=dtype,\n        dequant=dequant,\n    )\n\n\n@rocm_only\n@pytest.mark.parametrize(\n    \"op\", [fmha.ck_splitk.FwOp_S1, fmha.ck_splitk.FwOp_S2, fmha.ck_splitk.FwOp_S4]\n)\n@pytest.mark.parametrize(\"dtype\", [\"f32\"])\n@pytest.mark.parametrize(\"kv_heads\", [None, 1, 2], ids=_kv_heads_label)\n@pytest.mark.parametrize(\"n_heads\", [16])\n@pytest.mark.parametrize(\"d\", [128, 256])\n@pytest.mark.parametrize(\"padding, bsz\", [(32, 8), (4096, 1), (32, 1), (4096, 8)])\ndef test_ck_splitk_decoder(\n    op,\n    kv_heads: Optional[int],\n    n_heads: int,\n    padding: int,\n    bsz: int,\n    dtype: str,\n    d: int,\n) -> None:\n    # no quantized impl compared to cuda\n    _test_decoder(\n        op,\n        kv_heads=kv_heads,\n        n_heads=n_heads,\n        padding=padding,\n        bsz=bsz,\n        dtype=dtype,\n        d=d,\n    )\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        fmha.triton_splitk.FwOp_S1,\n        fmha.triton_splitk.FwOp_S2,\n    ],\n    ids=lambda op: f\"splitk{op.SPLIT_K}\",\n)\n@pytest.mark.parametrize(\"multiquery\", [True, False], ids=lambda x: \"mq\" if x else \"\")\n# n_heads=1 => it's ambiguous whether can count as multiquery\n@pytest.mark.parametrize(\"padding, bsz\", [(32, 8), (44, 1)])\n@pytest.mark.parametrize(\"dtype\", [\"f16\", \"bf16\"])\n@pytest.mark.parametrize(\"n_heads, num_queries\", [(2, 4), (2, 5), (6, 7), (20, 3)])\ndef test_triton_splitk_decoder_manyqueries(\n    op,\n    multiquery: bool,\n    n_heads: int,\n    padding: int,\n    bsz: int,\n    dtype: str,\n    num_queries: int,\n) -> None:\n    kv_heads = 1 if multiquery else None\n    _test_decoder(\n        op,\n        kv_heads=kv_heads,\n        n_heads=n_heads,\n        padding=padding,\n        bsz=bsz,\n        dtype=dtype,\n        num_queries=num_queries,\n        dequant=False,\n    )\n\n\ndef test_attn_bias_from_seqlens() -> None:\n    bias = fmha.attn_bias.BlockDiagonalMask.from_seqlens([3, 5, 1])\n    out = bias.split(torch.randn([1, 3 + 5 + 1, 16]))\n    assert len(out) == 3\n    assert tuple(out[0].shape) == (1, 3, 16)\n\n\n@cuda_only\ndef test_attn_bias_blockdiag_doc() -> None:\n    \"\"\"IMPORTANT:\n    This is the example in the doc for `BlockDiagonalMask`.\n    If this example needs to be updated, please also update the doc\n    \"\"\"\n    import torch\n\n    from xformers.ops import fmha\n\n    if torch.version.hip:\n        pytest.skip(\"backward pass/gradience is not yet supported by ck-tiled fmha!\")\n\n    K = 16\n    dtype = torch.float16\n    device = \"cuda\"\n    list_x = [\n        torch.randn([1, 3, 1, K], dtype=dtype, device=device),\n        torch.randn([1, 6, 1, K], dtype=dtype, device=device),\n        torch.randn([1, 2, 1, K], dtype=dtype, device=device),\n    ]\n    attn_bias, x = fmha.attn_bias.BlockDiagonalMask.from_tensor_list(list_x)\n\n    linear = torch.nn.Linear(K, K * 3).to(device=device, dtype=dtype)  # type: ignore\n\n    q, k, v = linear(x).reshape([1, -1, 1, 3, K]).unbind(-2)\n    out = fmha.memory_efficient_attention(q, k, v, attn_bias=attn_bias)\n    list_out = attn_bias.split(out)\n    assert tuple(list_out[0].shape) == (1, 3, 1, K)\n\n\n@cuda_only\nclass TestAttnBias:\n    @staticmethod\n    def create_tensors(\n        dtype,\n        B: int = 2,\n        Mq: int = 32,\n        Mkv: int = 32,\n        H: int = 3,\n        K: int = 16,\n        Kv: int = 16,\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:\n        return (\n            torch.randn([B, Mq, H, K], device=\"cuda\", dtype=dtype) * 3,\n            torch.randn([B, Mkv, H, K], device=\"cuda\", dtype=dtype) * 3,\n            torch.randn([B, Mkv, H, Kv], device=\"cuda\", dtype=dtype) * 3,\n            torch.randn([B, H, Mq, Mkv], device=\"cuda\", dtype=dtype) * 3,\n        )\n\n    @staticmethod\n    def pad_bias(bias: torch.Tensor) -> torch.Tensor:\n        align_to = 16\n        if (bias.shape[-1] % align_to) == 0:\n            return bias\n        pad_count = align_to - (bias.shape[-1] % align_to)\n        return torch.nn.functional.pad(bias, [0, pad_count])[:, :, :, : bias.shape[-1]]\n\n    @skip_if_sm100_or_better\n    def test_f16_biasf32(self) -> None:\n        q, k, v, bias = self.create_tensors(torch.float16)\n        fmha.memory_efficient_attention(q, k, v, attn_bias=bias)\n        bias = bias.to(torch.float32)\n        with pytest.raises((ValueError, RuntimeError)):\n            fmha.memory_efficient_attention(q, k, v, attn_bias=bias)\n\n    @skip_if_sm100_or_better\n    @disable_on_rocm\n    def test_f32_biasf16(self) -> None:\n        q, k, v, bias = self.create_tensors(torch.float32)\n        fmha.memory_efficient_attention(q, k, v, attn_bias=bias)\n        bias = bias.to(torch.float16)\n        with pytest.raises((ValueError, RuntimeError)):\n            fmha.memory_efficient_attention(q, k, v, attn_bias=bias)\n\n    @skip_if_sm100_or_better\n    @pytest.mark.parametrize(\"dtype\", [torch.float32, torch.float16])\n    def test_wrong_alignment(self, dtype) -> None:\n        op = fmha.cutlass.FwOp if torch.version.cuda else fmha.ck.FwOp\n        if dtype not in op.SUPPORTED_DTYPES:\n            pytest.skip(\n                f\"{dtype=} is not supported by {op.__module__}.{op.__qualname__}\"\n            )\n\n        q, k, v, bias = self.create_tensors(dtype, Mq=7, Mkv=5)\n        try:\n            fmha.memory_efficient_attention(q, k, v, attn_bias=bias, op=(op, None))\n            return\n        except (ValueError, RuntimeError):\n            pass\n        # This case is not supported, likely due to padding issues\n        # Let's make sure it works with padding\n        assert bias.ndim == 4, bias.shape\n        bias_padded = self.pad_bias(bias)\n        out = fmha.memory_efficient_attention(\n            q, k, v, attn_bias=bias_padded, op=(op, None)\n        ).float()\n        ref_out = ref_attention_bmhk_for_test(q, k, v, bias)\n        assert_allclose(\n            out, ref_out, atol=op.ERROR_ATOL[dtype], rtol=op.ERROR_RTOL[dtype]\n        )\n\n    def test_permuted_attn_bias(self) -> None:\n        op = fmha.cutlass.FwOp\n        dtype = torch.float16\n        q, k, v, bias = self.create_tensors(dtype, Mq=7, Mkv=7)\n        bias = bias.transpose(-1, -2)  # now `stride(-1) != 1`\n        # Either it works, or it raises an exception\n        # but we should never get a CUDA error\n        try:\n            out = fmha.memory_efficient_attention(\n                q, k, v, attn_bias=bias, op=(op, None)\n            ).float()\n            ref_out = ref_attention_bmhk_for_test(q, k, v, bias)\n            assert_allclose(\n                out, ref_out, atol=op.ERROR_ATOL[dtype], rtol=op.ERROR_RTOL[dtype]\n            )\n        except (ValueError, RuntimeError):\n            pass\n\n\nSM_AND_SHMEM_KBYTES = [\n    # https://docs.nvidia.com/cuda/cuda-c-programming-guide/#features-and-technical-specifications-technical-specifications-per-compute-capability\n    (50, 64),\n    (60, 64),\n    (70, 96),\n    (75, 64),\n    (80, 163),\n    (86, 99),\n    (89, 99),\n    # (90, 227),\n]\n\n\ndef test_window_size_materialize() -> None:\n    seqlens = [4, 6]\n    attn_bias = fmha.attn_bias.BlockDiagonalMask.from_seqlens(\n        q_seqlen=seqlens,\n        kv_seqlen=seqlens,\n    ).make_local_attention(2)\n    mask = attn_bias.materialize(\n        (1, 1, sum(seqlens), sum(seqlens)),\n        device=\"cpu\",\n        dtype=torch.float32,\n    )\n    true_mask = torch.log(\n        torch.Tensor(\n            [\n                [\n                    [\n                        [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n                        [1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n                        [0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n                        [0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0],\n                        [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0],\n                    ]\n                ]\n            ]\n        )\n    )\n    assert torch.all(mask == true_mask)\n\n\n@cuda_or_mtia_only\n@pytest.mark.parametrize(\"Mq\", [1, 512])\n@pytest.mark.parametrize(\n    \"opFW_biasT\",\n    [\n        (op, biasT)\n        for op in ALL_FW_OPS\n        for biasT in get_supported_attn_bias_types(op)\n        if op.SUPPORTS_BMGHK\n    ],\n    ids=lambda o: f\"{o[0].NAME}-{o[1].__name__}\" if isinstance(o, tuple) else \"\",\n)\ndef test_forward_gqa(opFW_biasT, Mq: int):\n    device = torch._C._get_accelerator().type\n\n    opFW, biasT = opFW_biasT\n    if Mq < 512 and (\n        issubclass(biasT, fmha.attn_bias.LowerTriangularMask)\n        or issubclass(biasT, fmha.attn_bias.BlockDiagonalCausalMask)\n    ):\n        pytest.skip(\"undefined upper left\")\n    B_Mq_Mkv_H_K_Kv = (3, Mq, 512, 16, 128, 128)\n    test_forward(\n        (\n            opFW,\n            device,\n            torch.float16,\n            biasT,\n            *B_Mq_Mkv_H_K_Kv,\n        ),\n        packed=False,\n        fmt=\"BMGHK\",\n        g=2,\n    )\n\n\n@cuda_or_mtia_only\n@pytest.mark.parametrize(\n    \"opBW\",\n    [\n        fmha.flash.BwOp,\n        fmha.ck.BwOp if torch.version.hip else fmha.cutlass.BwOp,\n        fmha.cutlass_blackwell.BwOp,\n    ],\n)\ndef test_backward_gqa(opBW):\n    device = torch._C._get_accelerator().type\n\n    H = 8\n    B_Mq_Mkv_H_K_Kv = (3, 512, 512, H, 128, 128)\n    dtype = torch.float16\n    query, key, value, attn_bias = create_tensors(\n        *(opBW, device, dtype, type(None), *B_Mq_Mkv_H_K_Kv),\n        attn_bias_requires_grad=False,\n        fmt=\"BMHK\",\n    )\n    op = (fmha.ck.FwOp if torch.version.hip else fmha.cutlass.FwOp, opBW)\n    key = key[:, :, :1].expand(-1, -1, H, -1)\n    value = value[:, :, :1].expand(-1, -1, H, -1)\n    key.requires_grad_(True)\n    out = fmha.memory_efficient_attention(query, key, value, attn_bias=attn_bias)\n    out.backward(query)\n    dk = key.grad\n    key.grad = None\n\n    if use_cpu_ref(device):\n        query = query.detach().cpu()\n        key = key.detach().cpu()\n        value = value.detach().cpu()\n        query.requires_grad_(True)\n        key.requires_grad_(True)\n        value.requires_grad_(True)\n\n    out_ref = ref_attention_bmhk_for_test(query, key, value, attn_bias=attn_bias)\n    out_ref.backward(query)\n\n    assert_allclose(\n        out.float().to(out_ref.device),\n        out_ref.float(),\n        atol=op[0].ERROR_ATOL[dtype],\n        rtol=op[0].ERROR_RTOL[dtype],\n    )\n    assert_allclose(\n        dk.float().to(key.grad.device),\n        key.grad.float(),\n        atol=op[1].ERROR_ATOL[dtype],\n        rtol=op[1].ERROR_RTOL[dtype],\n    )\n\n\n@cuda_or_mtia_only\n@pytest.mark.parametrize(\"opFW\", [op for op in ALL_FW_OPS if op.SUPPORTS_BMGHK])\ndef test_forward_gqa_one_group(opFW):\n    device = torch._C._get_accelerator().type\n\n    dtype = torch.float16\n    B, Mq, Mkv, H, K = 3, 13, 16, 5, 128\n    q = torch.randn([B, Mq, 1, H, K], dtype=dtype, device=device) * 3\n    k = torch.randn([B, Mkv, 1, H, K], dtype=dtype, device=device) * 3\n    v = torch.randn([B, Mkv, 1, H, K], dtype=dtype, device=device) * 3\n\n    supported = opFW.supports(fmha.Inputs(q, k, v))\n    if not supported:\n        supported_bmhk = opFW.supports(fmha.Inputs(q[:, :, 0], k[:, :, 0], v[:, :, 0]))\n        assert supported == supported_bmhk\n        pytest.skip(\"not supported\")\n    out = fmha.memory_efficient_attention_forward(q, k, v, op=opFW)\n    ref = ref_attention_for_test(q, k, v)\n    assert_allclose(\n        out.float(),\n        ref,\n        atol=opFW.ERROR_ATOL[dtype],\n        rtol=opFW.ERROR_RTOL.get(dtype, 1e-5),\n    )\n\n\n@sm80_or_better_only\n@disable_on_rocm\ndef test_flash_gqa_wrong_strides() -> None:\n    op = (fmha.flash.FwOp, None)\n\n    device = \"cuda\"\n    B, Mq, Mkv, G, H, K = 3, 1, 512, 2, 8, 128\n    q = torch.empty((B, Mq, G, H, K), dtype=torch.float16, device=device)\n    kv = torch.empty((B, Mkv, G, H, K), dtype=torch.float16, device=device)\n    fmha.memory_efficient_attention(q, kv, kv, op=op)\n\n    kv = torch.empty((B, Mkv, H, G, K), dtype=torch.float16, device=device).permute(\n        0, 1, 3, 2, 4\n    )\n    with pytest.raises(ValueError):\n        fmha.memory_efficient_attention(q, kv, kv, op=op)\n\n    kv = torch.empty((B, Mkv, G, 1, K), dtype=torch.float16, device=device)\n    with pytest.raises(ValueError):\n        fmha.memory_efficient_attention(q, kv, kv, op=op)\n    kv = kv.expand(-1, -1, -1, H, K)\n    fmha.memory_efficient_attention(q, kv, kv, op=op)\n\n    kv = torch.empty((B, Mkv, G, H, 2 * K), dtype=torch.float16, device=device)[\n        :, :, :, :, :K\n    ]\n    fmha.memory_efficient_attention(q, kv, kv, op=op)\n\n\ndef _dispatches_to_splitK(q, kv):\n    return (\n        _dispatch_fw_priority_list(fmha.Inputs(q, kv, kv), False)[0]\n        is fmha.triton_splitk.FwOp\n    )\n\n\ndef _dispatches_to_flash_decoding(q, kv):\n    return (\n        _dispatch_fw_priority_list(fmha.Inputs(q, kv, kv), False)[0] is fmha.flash.FwOp\n    )\n\n\n@disable_on_rocm\ndef test_dispatch_decoding_bmhk() -> None:\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 8, 1, 128]), torch.empty([1, 2048, 1, 128])\n    ), \"Should not use SplitK with 1 head (no tensorcores)\"\n    assert _dispatches_to_flash_decoding(\n        torch.empty([1, 8, 32, 128]),\n        torch.empty([1, 2048, 1, 128]).expand(-1, -1, 32, -1),\n    ), \"Should use Flash-Decoding with BMHK MQA\"\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 8, 32, 128]),\n        torch.empty([1, 2048, 32, 128]),\n    ), \"Should not use SplitK when no TensorCores\"\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 128, 32, 128]),\n        torch.empty([1, 2048, 1, 128]).expand(-1, -1, 32, -1),\n    ), \"Should not use SplitK if q seqlen is long\"\n    assert not _dispatches_to_splitK(\n        torch.empty([128, 8, 32, 128]),\n        torch.empty([128, 2048, 1, 128]).expand(-1, -1, 32, -1),\n    ), \"Should not use SplitK if B is big\"\n\n\n@disable_on_rocm\ndef test_dispatch_decoding_bmghk() -> None:\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 8, 1, 1, 128]), torch.empty([1, 2048, 1, 1, 128])\n    ), \"Should not use SplitK with 1 head (no tensorcores)\"\n    assert _dispatches_to_flash_decoding(\n        torch.empty([1, 8, 1, 32, 128]),\n        torch.empty([1, 2048, 1, 1, 128]).expand(-1, -1, -1, 32, -1),\n    ), \"Should use Flash-Decoding with MQA\"\n    assert _dispatches_to_flash_decoding(\n        torch.empty([1, 8, 4, 32, 128]),\n        torch.empty([1, 2048, 4, 1, 128]).expand(-1, -1, -1, 32, -1),\n    ), \"Should use Flash-Decoding with GQA\"\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 8, 1, 32, 128]),\n        torch.empty([1, 2048, 1, 32, 128]),\n    ), \"Should not use SplitK when no TensorCores\"\n    assert not _dispatches_to_splitK(\n        torch.empty([1, 128, 1, 32, 128]),\n        torch.empty([1, 2048, 1, 1, 128]).expand(-1, -1, -1, 32, -1),\n    ), \"Should not use SplitK if q seqlen is long\"\n    assert not _dispatches_to_splitK(\n        torch.empty([128, 8, 1, 32, 128]),\n        torch.empty([128, 2048, 1, 1, 128]).expand(-1, -1, -1, 32, -1),\n    ), \"Should not use SplitK if B is big\"\n\n\nshapes_triton_splitk = [\n    (1, 8, 2**16, 1, 128, 128),\n    (1, 4, 2**16, 1, 128, 128),\n    (1, 16, 2**16, 1, 128, 128),\n    (1, 16, 2**16, 1, 32, 32),\n    (1, 8, 1025, 1, 128, 128),\n    (2, 8, 4096, 1, 128, 128),\n    (10, 8, 2**16, 1, 128, 128),\n    (10, 15, 2**16, 1, 128, 128),\n    (1, 3, 2**16, 1, 128, 128),\n    (1, 3, 2**16 - 10, 1, 128, 128),\n    (2, 3, 73, 1, 128, 128),\n    (2, 7, 7328, 1, 128, 128),\n    (2, 7, 7328, 1, 120, 120),\n    (2, 7, 63, 1, 120, 120),\n]\nop_device_dtype_biasT_B_Mq_Mkv_H_K_Kv_splitk = [\n    (fmha.triton_splitk.FwOp, \"cuda\", torch.float16, type(None), *s)\n    for s in shapes_triton_splitk\n] + [\n    (fmha.triton_splitk.FwOp, \"cuda\", torch.bfloat16, type(None), *s)\n    for s in shapes_triton_splitk\n]\n\n\n@pytest.mark.parametrize(\n    \"opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv\",\n    op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv_splitk,\n    ids=[make_id(*c) for c in op_device_dtype_biasT_B_Mq_Mkv_H_K_Kv_splitk],\n)\n@cuda_only\ndef test_forward_splitk(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n    packed=False,\n    fmt=\"BMHK\",\n):\n    test_forward(opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv, packed=packed, fmt=fmt)\n\n\n@cuda_or_mtia_only\n@pytest.mark.parametrize(\n    \"op\",\n    [fmha.triton_splitk.FwOp, fmha.flash.FwOp, fmha.ck.FwOp],\n    ids=lambda op: op.NAME,\n)\n@pytest.mark.parametrize(\"dtype\", [torch.float16, torch.bfloat16], ids=str)\n@pytest.mark.parametrize(\n    \"B_Mkv_H_K\",\n    [\n        (1, 2**16, 3, 128),\n        (5, 53, 4, 64),\n        (7, 51, 4, 256),\n        (3, 51, 2, 512),\n    ],\n)\ndef test_mqa_decoding(op: Type[fmha.AttentionFwOpBase], dtype, B_Mkv_H_K):\n    device = torch._C._get_accelerator().type\n\n    B, Mkv, H, K = B_Mkv_H_K\n    q = torch.randn([B, 1, H, K], dtype=dtype, device=device) * 3\n    k = torch.randn([B, Mkv, 1, K], dtype=dtype, device=device) * 3\n    v = torch.randn([B, Mkv, 1, K], dtype=dtype, device=device) * 3\n    k = k.expand(-1, -1, H, -1)\n    v = v.expand(-1, -1, H, -1)\n\n    if skip_reasons := op.not_supported_reasons(fmha.Inputs(q, k, v)):\n        pytest.skip(\"; \".join(skip_reasons))\n    out = fmha.memory_efficient_attention_forward(q, k, v, op=op)\n    ref = ref_attention_for_test(q, k, v)\n    assert_allclose(\n        out.float(),\n        ref,\n        atol=op.ERROR_ATOL[dtype],\n        rtol=op.ERROR_RTOL.get(dtype, 1e-5),\n    )\n\n\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_empty_tensors_empty_query(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n):\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=\"BMHK\",\n    )\n    opFW = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv[0]\n\n    if torch.version.hip:\n        pytest.skip(\"backward pass/gradience is not yet supported by ck-tiled fmha!\")\n\n    query = query[:, :0]\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n    out = xformers.ops.memory_efficient_attention(query, key, value, op=(opFW, None))\n    assert out.shape[1] == 0\n    out.backward(out)\n    # dK/dV should be all zeros\n    assert_allclose(key.grad, torch.zeros_like(key.grad), \"key.grad\")\n    assert_allclose(value.grad, torch.zeros_like(value.grad), \"value.grad\")\n\n\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_empty_tensors_empty_kv(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n):\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=\"BMHK\",\n    )\n    opFW = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv[0]\n    if opFW == fmha.triton_splitk.FwOp:\n        pytest.skip(\"triton_splitk doesn't support empty kv\")\n\n    if torch.version.hip:\n        pytest.skip(\"backward pass/gradience is not yet supported by ck-tiled fmha!\")\n\n    key = key[:, :0]\n    value = value[:, :0]\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n    out = xformers.ops.memory_efficient_attention(query, key, value, op=(opFW, None))\n    assert_allclose(out, torch.zeros_like(out), \"out\")\n    out.backward(out)\n    # dQ should be all zeros\n    assert_allclose(query.grad, torch.zeros_like(query.grad), \"query.grad\")\n\n\n@parametrize_opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv__xs\ndef test_empty_tensors_empty_b(\n    opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n):\n    query, key, value, attn_bias = create_tensors(\n        *opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv,\n        fmt=\"BMHK\",\n    )\n    opFW = opFW_device_dtype_biasT_B_Mq_Mkv_H_K_Kv[0]\n\n    if torch.version.hip:\n        pytest.skip(\"backward pass/gradience is not yet supported by ck-tiled fmha!\")\n\n    query, key, value = query[:0], key[:0], value[:0]\n    query.requires_grad_(True)\n    key.requires_grad_(True)\n    value.requires_grad_(True)\n    out = xformers.ops.memory_efficient_attention(query, key, value, op=(opFW, None))\n    out.backward(out)\n\n\ndef test_local_attn_bias() -> None:\n    mask = (\n        fmha.attn_bias.LocalAttentionFromBottomRightMask(window_left=1, window_right=2)\n        .materialize(shape=(4, 4))\n        .exp()\n    )\n\n    expected = torch.tensor(\n        [[1, 1, 1, 0], [1, 1, 1, 1], [0, 1, 1, 1], [0, 0, 1, 1]], dtype=torch.float32\n    )\n    assert (mask == expected).all().item()\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\"B\", [1, 5, 128])\n@pytest.mark.parametrize(\"MAX_T\", [64, 128, 2048, 4096, 8192])\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        fmha.triton_splitk.FwOp,\n        fmha.triton_splitk.FwOp_S8,\n        fmha.triton_splitk.FwOp_Map[48],\n    ],\n    ids=lambda op: op.NAME,\n)\n@pytest.mark.parametrize(\"num_quant_groups\", [0, 1, 8])\n@pytest.mark.parametrize(\"page_size\", [64, 128, 256])\n@pytest.mark.parametrize(\"gappy\", [False, True], ids=lambda x: \"gappy\" if x else \"\")\ndef test_paged_attention(\n    B,\n    MAX_T: int,\n    num_quant_groups: int,\n    page_size: int,\n    op: Type[AttentionFwOpBase],\n    gappy: bool,\n):\n    msg = \"Notional padding should be divisible by the page size\"\n    if gappy and MAX_T % page_size:\n        context = pytest.raises(ValueError, match=msg)\n    else:\n        context = nullcontext()  # type: ignore[assignment]\n    with context:\n        paged_attention_run_inner(\n            B, MAX_T, num_quant_groups, page_size, op, bench=False, gappy=gappy\n        )\n\n\n@rocm_only\n@pytest.mark.parametrize(\"B\", [1, 5, 128])\n@pytest.mark.parametrize(\"MAX_T\", [64, 128, 2048, 4096, 8192])\n@pytest.mark.parametrize(\"page_size\", [128, 256])\n@pytest.mark.parametrize(\"gappy\", [False, True], ids=lambda x: \"gappy\" if x else \"\")\ndef test_paged_attention_ck(B, MAX_T: int, page_size: int, gappy: bool):\n    op = fmha.ck.FwOp\n    num_quant_groups = 0\n    msg = \"Notional padding should be divisible by the page size\"\n    if gappy and MAX_T % page_size:\n        context = pytest.raises(ValueError, match=msg)\n    else:\n        context = nullcontext()  # type: ignore[assignment]\n    with context:\n        paged_attention_run_inner(\n            B, MAX_T, num_quant_groups, page_size, op, bench=False, gappy=gappy\n        )\n\n\n@sm80_or_better_only\n@disable_on_rocm\n@pytest.mark.parametrize(\"B\", [1, 5, 128])\n@pytest.mark.parametrize(\"MAX_T\", [64, 128, 2048, 4096, 8192])\n@pytest.mark.parametrize(\"page_size\", [256])\ndef test_paged_attention_flash(B, MAX_T: int, page_size: int):\n    # TODO: add smaller page sizes when https://github.com/Dao-AILab/flash-attention/pull/824 is merged\n    op = fmha.flash.FwOp\n    if (\n        fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask\n        not in get_supported_attn_bias_types(op)\n    ):\n        pytest.skip(\"Not supported bias\")\n    num_quant_groups = 0\n    paged_attention_run_inner(B, MAX_T, num_quant_groups, page_size, op, bench=False)\n\n\n@skip_if_sm100_or_better\n@sm90_or_better_only\n@disable_on_rocm\n@pytest.mark.parametrize(\n    \"op\", _filter_unsupported_ops([fmha.flash3.FwOp, fmha.flash3.FwOp_KVSplit])\n)\n@pytest.mark.parametrize(\"B\", [1, 5, 128])\n@pytest.mark.parametrize(\"MAX_T\", [64, 128, 2048, 4096, 8192])\n@pytest.mark.parametrize(\"page_size\", [256])\ndef test_paged_attention_flash3(\n    op: Type[AttentionFwOpBase], B: int, MAX_T: int, page_size: int\n):\n    if (\n        fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask\n        not in get_supported_attn_bias_types(op)\n    ):\n        pytest.skip(\"Not supported bias\")\n    num_quant_groups = 0\n    paged_attention_run_inner(B, MAX_T, num_quant_groups, page_size, op, bench=False)\n\n\ndef paged_attention_run_inner(\n    B: int,\n    MAX_T: int,\n    num_quant_groups: int,\n    page_size: int,\n    op: Type[AttentionFwOpBase],\n    bench: bool,\n    gappy: bool = False,\n) -> None:\n    import triton\n\n    torch.manual_seed(10)\n    TEST_WARMUP_MS = 500\n    TEST_RUN_MS = 5000\n\n    N_H_L = 8\n    N_KVH_L = 1\n    D_H = 128\n    D_H_KV = D_H // 8 + num_quant_groups if num_quant_groups else D_H\n    kv_seqlens = torch.randint(low=1, high=MAX_T + 1, size=(B,)).tolist()\n    # Paged attention requires k.shape[1] and v.shape[1] to be divisible by page_size, so pad\n    padded_per_row_len = ((MAX_T + page_size - 1) // page_size) * page_size\n\n    if gappy:\n        make_paged_kwargs = {\n            \"paged_type\": fmha.attn_bias.PagedBlockDiagonalGappyKeysMask,\n            \"notional_padding\": MAX_T,\n        }\n        attn_bias = fmha.attn_bias.BlockDiagonalGappyKeysMask.from_seqlens(\n            q_seqlen=[1] * B,\n            kv_seqstarts=list(range(0, MAX_T * (B + 1), MAX_T)),\n            kv_seqlen=kv_seqlens,\n        )\n    else:\n        make_paged_kwargs = {\n            \"paged_type\": fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        }\n\n        block_type = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask\n        attn_bias = block_type.from_seqlens(  # type: ignore\n            q_seqlen=[1] * B,\n            kv_padding=MAX_T,\n            kv_seqlen=kv_seqlens,\n        )\n\n    q = torch.randn((B, 1, N_H_L, D_H), dtype=torch.bfloat16, device=\"cuda\")\n    if num_quant_groups:\n        if triton.__version__[:4] < \"3.0.\":\n            raise pytest.skip(\"dequant needs triton updates\")\n\n        # Using high=64 below, because with 256 both paged and non-paged paths\n        # will produce NaNs - probably some quantization coeffitions are NaNs\n        # after the bitwise cast.\n        cache_k = torch.randint(\n            0, 64, (B, MAX_T, N_KVH_L, D_H_KV * 4), dtype=torch.uint8, device=\"cuda\"\n        )\n        cache_k = cache_k.view(dtype=torch.int32)\n        cache_v = torch.randint(\n            0, 64, (B, MAX_T, N_KVH_L, D_H_KV * 4), dtype=torch.uint8, device=\"cuda\"\n        )\n        cache_v = cache_v.view(dtype=torch.int32)\n\n        op = type(\n            f\"{op.__name__}_{num_quant_groups}\",\n            (op,),\n            {\"NUM_GROUPS\": num_quant_groups},\n        )\n    else:\n        cache_k = torch.randn(\n            (B, MAX_T, N_KVH_L, D_H), dtype=torch.bfloat16, device=\"cuda\"\n        )\n        cache_v = torch.randn_like(cache_k)\n\n    axq = q.view(1, B * 1, N_H_L, D_H)\n    axk = cache_k.view(1, B * MAX_T, N_KVH_L, D_H_KV).expand(\n        1, B * MAX_T, N_H_L, D_H_KV\n    )\n    axv = cache_v.view(1, B * MAX_T, N_KVH_L, D_H_KV).expand(\n        1, B * MAX_T, N_H_L, D_H_KV\n    )\n\n    k_cache_size_usual = axk.numel()\n\n    # First, create \"wasteful\" K/V cache, where every block in logical cache\n    # has a physical representation, even if there's nothing stored there\n\n    block_tables = torch.arange(\n        B * padded_per_row_len // page_size, device=\"cuda\", dtype=torch.int32\n    ).reshape(B, -1)\n\n    shape_padded = (B, padded_per_row_len, N_KVH_L, D_H_KV)\n    axk_padded = torch.empty(shape_padded, device=axk.device, dtype=axk.dtype)\n    axv_padded = torch.empty(shape_padded, device=axv.device, dtype=axv.dtype)\n    axk_padded[:, :MAX_T] = axk.view(B, -1, N_H_L, D_H_KV)[:, :, :1, :]\n    axv_padded[:, :MAX_T] = axv.view(B, -1, N_H_L, D_H_KV)[:, :, :1, :]\n\n    axk_padded = axk_padded.view(1, B * padded_per_row_len, N_KVH_L, D_H_KV)\n    axv_padded = axv_padded.view(1, B * padded_per_row_len, N_KVH_L, D_H_KV)\n\n    axk_padded = axk_padded.expand(-1, -1, N_H_L, -1)\n    axv_padded = axv_padded.expand(-1, -1, N_H_L, -1)\n\n    attn_bias_paged = attn_bias.make_paged(\n        block_tables=block_tables,\n        page_size=page_size,\n        **make_paged_kwargs,  # type: ignore\n    )\n    if type(attn_bias_paged) not in op.SUPPORTED_ATTN_BIAS_TYPES:\n        pytest.skip(f\"{type(attn_bias_paged)} not supported\")\n    if type(attn_bias) not in op.SUPPORTED_ATTN_BIAS_TYPES:\n        pytest.skip(f\"{type(attn_bias_paged)} not supported\")\n    y_usual = fmha.memory_efficient_attention_forward(\n        axq,\n        axk,\n        axv,\n        attn_bias,\n        op=op,\n    )\n    if bench:\n        g = torch.cuda.CUDAGraph()\n        with torch.cuda.graph(g):\n            y_usual = fmha.memory_efficient_attention_forward(\n                axq,\n                axk,\n                axv,\n                attn_bias,\n                op=op,\n            )\n        t_ms = triton.testing.do_bench(\n            lambda g=g: g.replay(),\n            warmup=TEST_WARMUP_MS,\n            rep=TEST_RUN_MS,\n        )\n        logger.info(f\"Non-paged attention took {t_ms * 1e3:.2f}us\")\n\n    y_wasteful = fmha.memory_efficient_attention_forward(\n        axq,\n        axk_padded,\n        axv_padded,\n        attn_bias_paged,\n        op=op,\n    )\n    if bench:\n        g = torch.cuda.CUDAGraph()\n        with torch.cuda.graph(g):\n            y_wasteful = fmha.memory_efficient_attention_forward(\n                axq,\n                axk_padded,\n                axv_padded,\n                attn_bias_paged,\n                op=op,\n            )\n        t_ms = triton.testing.do_bench(\n            lambda g=g: g.replay(),\n            warmup=TEST_WARMUP_MS,\n            rep=TEST_RUN_MS,\n        )\n        logger.info(f\"Paged attention with wasteful K/V-cache took {t_ms * 1e3:.2f}us\")\n\n    torch.testing.assert_close(\n        y_wasteful,\n        y_usual,\n        atol=1.0e-2,\n        rtol=1.0e-2,\n    )\n\n    # Now let's create a \"packed\" K/V cache, where only meaniningful logical blocks are mapped to physical blocks\n    block_tables, packed_cache_k, packed_cache_v = pack_kv_cache(\n        cache_k,\n        cache_v,\n        kv_seqlens,\n        page_size,\n    )\n    attn_bias_paged = attn_bias.make_paged(\n        block_tables=block_tables,\n        page_size=page_size,\n        **make_paged_kwargs,  # type: ignore\n    )\n    axk = packed_cache_k.view(1, -1, N_KVH_L, D_H_KV).expand(1, -1, N_H_L, D_H_KV)\n    axv = packed_cache_v.view(1, -1, N_KVH_L, D_H_KV).expand(1, -1, N_H_L, D_H_KV)\n\n    k_cache_size_packed = axk.numel()\n\n    y_packed = fmha.memory_efficient_attention_forward(\n        axq,\n        axk,\n        axv,\n        attn_bias_paged,\n        op=op,\n    )\n\n    logger.info(\n        f\"KV-cache size reduced by {(100 * (1 - k_cache_size_packed/k_cache_size_usual)):.2f}%\"\n    )\n\n    torch.testing.assert_close(y_wasteful, y_packed)\n\n    # Let's swap two blocks, and adjust two corresponding entries in the block table. The result shouldn't change\n    i, j = 0, axk.shape[1] // page_size - 1\n\n    axk = axk[:, :, :1, :]\n    axv = axv[:, :, :1, :]\n\n    vals_i = axk[:, i * page_size : (i + 1) * page_size, :, :].clone()\n    vals_j = axk[:, j * page_size : (j + 1) * page_size, :, :].clone()\n    axk[:, i * page_size : (i + 1) * page_size, :, :] = vals_j\n    axk[:, j * page_size : (j + 1) * page_size, :, :] = vals_i\n\n    vals_i = axv[:, i * page_size : (i + 1) * page_size, :, :].clone()\n    vals_j = axv[:, j * page_size : (j + 1) * page_size, :, :].clone()\n    axv[:, i * page_size : (i + 1) * page_size, :, :] = vals_j\n    axv[:, j * page_size : (j + 1) * page_size, :, :] = vals_i\n\n    axk = axk.expand(-1, -1, N_H_L, -1)\n    axv = axv.expand(-1, -1, N_H_L, -1)\n\n    where_i = block_tables == i\n    where_j = block_tables == j\n    block_tables.masked_fill_(where_i, j)\n    block_tables.masked_fill_(where_j, i)\n\n    y_swapped = fmha.memory_efficient_attention_forward(\n        axq,\n        axk,\n        axv,\n        attn_bias_paged,\n        op=op,\n    )\n    if bench:\n        g = torch.cuda.CUDAGraph()\n        with torch.cuda.graph(g):\n            y_swapped = fmha.memory_efficient_attention_forward(\n                axq,\n                axk,\n                axv,\n                attn_bias_paged,\n                op=op,\n            )\n        t_ms = triton.testing.do_bench(\n            lambda g=g: g.replay(),\n            warmup=TEST_WARMUP_MS,\n            rep=TEST_RUN_MS,\n        )\n        logger.info(f\"Paged attention with packed K/V-cache took {t_ms * 1e3:.2f}us\")\n\n    torch.testing.assert_close(y_swapped, y_packed)\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"bias_t\",\n    [None, fmha.attn_bias.LowerTriangularMask, fmha.attn_bias.BlockDiagonalMask],\n)\n@pytest.mark.parametrize(\"create_bias_inside_compiled\", [False, True])\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        None,\n        (fmha.flash.FwOp, fmha.flash.BwOp),\n        (fmha.flash3.FwOp, fmha.flash3.BwOp),\n        (fmha.cutlass_blackwell.FwOp, fmha.cutlass_blackwell.BwOp),\n    ],\n)\ndef test_memeff_compile(bias_t, create_bias_inside_compiled: bool, op) -> None:\n    torch.manual_seed(0)\n    if op is not None and not op[0].is_available():\n        pytest.skip(\"Op is not available\")\n    torch._dynamo.reset_code_caches()  # avoids hitting recompilation limit\n    B, M, H, K = 1, 256, 2, 64\n    q, k, v, bias = create_tensors(\n        op if op is None else op[0],\n        \"cuda\",\n        torch.float16,\n        bias_t,\n        B,\n        M,\n        M,\n        H,\n        K,\n        K,\n        fmt=\"BMHK\",\n    )\n    grad = torch.randn_like(q)\n    if create_bias_inside_compiled:\n        bias = None\n        if bias_t is not None:\n            pytest.skip(\"Can't create this mask inside compile\")\n    if bias is not None:\n        bias.to(q.device)\n    q.requires_grad_(True)\n    k.requires_grad_(True)\n    v.requires_grad_(True)\n\n    def fmha_fn(q, k, v, bias):\n        if create_bias_inside_compiled and bias_t is not None:\n            bias = bias_t()\n        return fmha.memory_efficient_attention(q, k, v, attn_bias=bias, op=op)\n\n    # Eager reference\n    out_ref = fmha_fn(q, k, v, bias)\n    out_ref.backward(grad)\n    dq_ref, dk_ref, dv_ref = q.grad, k.grad, v.grad\n    q.grad, k.grad, v.grad = None, None, None\n\n    # Compiled version\n    fmha_c = torch.compile(fmha_fn, fullgraph=True, dynamic=False)\n    out = fmha_c(q, k, v, bias)\n    out.backward(grad)\n\n    assert_allclose(\n        out,\n        out_ref,\n        \"out\",\n        atol=fmha.flash.FwOp.ERROR_ATOL[q.dtype],\n        rtol=fmha.flash.FwOp.ERROR_RTOL[q.dtype],\n    )\n    atol, rtol = (\n        fmha.flash.BwOp.ERROR_ATOL[q.dtype],\n        fmha.flash.BwOp.ERROR_RTOL[q.dtype],\n    )\n    assert_allclose(q.grad, dq_ref, \"dq\", atol=atol, rtol=rtol)\n    assert_allclose(k.grad, dk_ref, \"dk\", atol=atol, rtol=rtol)\n    assert_allclose(v.grad, dv_ref, \"dv\", atol=atol, rtol=rtol)\n\n\n@sm90_or_better_only\n@pytest.mark.parametrize(\"B\", [1, 16, 64])\n@pytest.mark.parametrize(\"Mkv\", [2048, 8192])\n@pytest.mark.parametrize(\"Hkv\", [1, 2])\n@pytest.mark.parametrize(\"G\", [1, 8])\n@pytest.mark.parametrize(\"page_size\", [64, 256])\n@torch.no_grad()\ndef test_triton_splitk_rowwise_fp8(\n    B: int,\n    Mkv: int,\n    Hkv: int,\n    G: int,\n    page_size: int,\n    Mq: int = 1,\n    K: int = 128,\n) -> None:\n    torch.manual_seed(10)\n\n    Hq = Hkv * G\n\n    device = torch.device(\"cuda\")\n    dtype = torch.bfloat16\n\n    inp, inp_ref, inp_fp8_paged, inp_bf16_paged = construct_fp8_attention_inputs(\n        B, Mkv, Mq, Hkv, Hq, K, page_size, device, dtype\n    )\n\n    (\n        attn_output_fp8,\n        context_fp8,\n    ) = fmha._memory_efficient_attention_forward_requires_grad(\n        inp, op=fmha.triton_splitk.FwOp\n    )\n\n    (\n        attn_output_ref,\n        context_ref,\n    ) = fmha._memory_efficient_attention_forward_requires_grad(\n        inp_ref, op=fmha.triton_splitk.FwOp\n    )\n\n    torch.testing.assert_close(attn_output_fp8, attn_output_ref, atol=5e-3, rtol=5e-3)\n    assert context_fp8 is not None and context_ref is not None\n    torch.testing.assert_close(context_fp8.lse, context_ref.lse, atol=5e-4, rtol=5e-4)\n\n    # Paged K/V cache\n\n    paged_bias = fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask\n    if paged_bias not in fmha.triton_splitk.FwOp.SUPPORTED_ATTN_BIAS_TYPES:\n        return\n\n    (\n        attn_output_fp8_paged,\n        context_fp8_paged,\n    ) = fmha._memory_efficient_attention_forward_requires_grad(\n        inp_fp8_paged, op=fmha.triton_splitk.FwOp\n    )\n    torch.testing.assert_close(\n        attn_output_fp8, attn_output_fp8_paged, atol=2e-3, rtol=2e-3\n    )\n    assert context_fp8_paged is not None\n    torch.testing.assert_close(\n        context_fp8.lse, context_fp8_paged.lse, atol=1e-4, rtol=1e-4\n    )\n\n\ndef fp8_per_head_quantize(\n    x: torch.Tensor,\n    dtype_fp8: torch.dtype,\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    MAX_FP8 = torch.finfo(dtype_fp8).max\n    EPS = 1e-12\n    SCALE_UP = 1200.0\n    tensor_max = torch.amax(torch.abs(x), dim=(1, 3), keepdim=False).to(torch.float32)\n    clamp_max = torch.clamp(tensor_max, min=EPS, max=SCALE_UP)\n    scale = MAX_FP8 / clamp_max  # Shape: [batch, num_heads]\n    x_quantized = (x * scale[:, None, :, None]).to(\n        dtype_fp8\n    )  # Shape: [B, seq_len, num_heads, head_dim]\n\n    return x_quantized, 1 / scale  # Shape: [batch, num_heads]\n\n\n@disable_on_rocm\n@sm90_or_better_only\n@pytest.mark.parametrize(\"dtype_init\", [torch.bfloat16])\n@pytest.mark.parametrize(\"deterministic\", [True])\n@pytest.mark.parametrize(\"causal\", [False, True])\n@pytest.mark.parametrize(\"B\", [4, 8, 16])\n@pytest.mark.parametrize(\"nheads\", [6, 16])\n@pytest.mark.parametrize(\"seq_len\", [256, 512, 1024])\n@pytest.mark.parametrize(\"head_dim\", [64, 128, 256])\ndef test_fp8_attention(dtype_init, deterministic, causal, B, nheads, seq_len, head_dim):\n    op = fmha.flash3.FwOp\n    if not op.is_available():\n        pytest.skip(\"FAv3 is not available\")\n    dtype_fp8 = torch.float8_e4m3fn\n    if dtype_fp8 not in op.SUPPORTED_DTYPES:\n        pytest.skip(\"FP8 is not supported\")\n\n    q = torch.randn(B, seq_len, nheads, head_dim, device=\"cuda\", dtype=dtype_init)\n    k = torch.randn(B, seq_len, nheads, head_dim, device=\"cuda\", dtype=dtype_init)\n    v = torch.randn(B, seq_len, nheads, head_dim, device=\"cuda\", dtype=dtype_init)\n\n    q_fp8, descale_q = fp8_per_head_quantize(q, dtype_fp8)\n    k_fp8, descale_k = fp8_per_head_quantize(k, dtype_fp8)\n    v_fp8, descale_v = fp8_per_head_quantize(v, dtype_fp8)\n\n    q_fp8_packed = pack_fp8_tensorwise_per_head(q_fp8, descale_q, dtype_init)\n    k_fp8_packed = pack_fp8_tensorwise_per_head(k_fp8, descale_k, dtype_init)\n    v_fp8_packed = pack_fp8_tensorwise_per_head(v_fp8, descale_v, dtype_init)\n\n    q_fp8_fake = q_fp8_packed.dequantize()\n    k_fp8_fake = k_fp8_packed.dequantize()\n    v_fp8_fake = v_fp8_packed.dequantize()\n\n    out_ref = fmha.memory_efficient_attention_forward(\n        q_fp8_fake, k_fp8_fake, v_fp8_fake, None, op=op\n    )\n\n    out = fmha.memory_efficient_attention_forward(\n        q_fp8_packed,\n        k_fp8_packed,\n        v_fp8_packed,\n        None,\n        op=op,\n    )\n\n    # NOTE: output dtype of FP8 attention is hard-code to BF16 for now: https://fburl.com/jcfiqmg0\n    assert out.dtype == torch.bfloat16, \"FP8 output is not BF16\"\n    torch.testing.assert_close(out, out_ref, atol=3e-2, rtol=1e-4)\n\n\ndef _pack_xformer_input(\n    q: torch.Tensor,\n    k: torch.Tensor,\n    v: torch.Tensor,\n    cache_seqlens: List[int],\n    bias_type,\n) -> Tuple[\n    torch.Tensor,\n    torch.Tensor,\n    torch.Tensor,\n    fmha.attn_bias.BlockDiagonalPaddedKeysMask,\n]:\n    batch, seq_len_q, head_q, head_d = q.shape\n    _, max_len_kv, head_kv, _ = k.shape\n\n    attn_bias = bias_type.from_seqlens(\n        q_seqlen=[seq_len_q] * batch,\n        kv_seqlen=cache_seqlens,\n        kv_padding=max_len_kv,\n    )\n\n    q = q.view(1, -1, head_q, head_d)\n    k = k.expand(-1, -1, head_q, -1).view(1, -1, head_q, k.shape[-1])\n    v = v.expand(-1, -1, head_q, -1).view(1, -1, head_q, v.shape[-1])\n    return q, k, v, attn_bias\n\n\n@disable_on_rocm\n@sm90_or_better_only\n@pytest.mark.parametrize(\"dtype_init\", [torch.bfloat16])\n@pytest.mark.parametrize(\"deterministic\", [True])\n@pytest.mark.parametrize(\"causal\", [False, True])\n@pytest.mark.parametrize(\"B\", [4, 8, 16])\n@pytest.mark.parametrize(\"nheads_q\", [8, 16])\n@pytest.mark.parametrize(\"seq_len_q\", [1, 2, 4, 8])\n@pytest.mark.parametrize(\"seq_len_kv\", [256, 512, 1024, 2048])\n@pytest.mark.parametrize(\"max_len_kv\", [4096, 8192])\n@pytest.mark.parametrize(\"head_dim\", [128])\n@pytest.mark.parametrize(\n    \"bias\",\n    [\n        fmha.attn_bias.BlockDiagonalPaddedKeysMask,\n        fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    ],\n)\ndef test_fav3_kvsplit_attn(\n    dtype_init,\n    deterministic,\n    causal,\n    B,\n    nheads_q,\n    seq_len_q,\n    seq_len_kv,\n    max_len_kv,\n    head_dim,\n    bias,\n):\n    op = fmha.flash3.FwOp_KVSplit\n    if not op.is_available():\n        pytest.skip(\"FAv3 KVSplit is not available\")\n    nheads_kv = 1\n\n    q = torch.randn(B, seq_len_q, nheads_q, head_dim, device=\"cuda\", dtype=dtype_init)\n    k = torch.randn(B, seq_len_kv, nheads_kv, head_dim, device=\"cuda\", dtype=dtype_init)\n    v = torch.randn(B, seq_len_kv, nheads_kv, head_dim, device=\"cuda\", dtype=dtype_init)\n\n    xq, xk, xv, attn_bias = _pack_xformer_input(q, k, v, [seq_len_kv] * B, bias)\n\n    out_ref, lse_ref = fmha.memory_efficient_attention_forward_requires_grad(\n        xq, xk, xv, attn_bias, op=fmha.flash3.FwOp\n    )\n\n    out, lse = fmha.memory_efficient_attention_forward_requires_grad(\n        xq,\n        xk,\n        xv,\n        attn_bias,\n        op=op,\n    )\n\n    torch.testing.assert_close(out, out_ref, atol=4e-3, rtol=1e-4)\n\n    torch.testing.assert_close(lse, lse_ref, atol=4e-3, rtol=1e-4)\n\n\n@sm90_or_better_only\n@pytest.mark.parametrize(\n    \"op\",\n    _filter_unsupported_ops(\n        (\n            [\n                fmha.flash.FwOp,\n                fmha.cutlass.FwOp,\n                fmha.flash3.FwOp,\n                fmha.flash3.FwOp_KVSplit,\n            ]\n            if not torch.version.hip\n            else [fmha.ck.FwOp]\n        )\n        + [fmha.triton_splitk.FwOp]\n    ),\n)\ndef test_nans_in_padding(op):\n    \"\"\"\n    Create a batch of sequences with variable lengths, stored in padded format,\n    and fill the unused positions in K/V with NaNs.\n    This shouldn't affect the result, but currently FA3 produces NaNs in the output.\n    The reason is probably that some sequences end in the middle of a K/V block,\n    and NaNs leak into quantities like per-block maximum of Q@K used in FA algorithm.\n    \"\"\"\n\n    if \"cuda\" not in _devices:\n        pytest.skip(\"CUDA device is not available\")\n\n    nheads_kv = 1\n    nheads_q = 8\n    B = 64\n    seq_len_q = 15\n    max_len_kv = 256\n    head_dim = 128\n    dtype = torch.bfloat16\n    page_size = 64\n\n    padded_per_row_len = ((max_len_kv + page_size - 1) // page_size) * page_size\n\n    assert padded_per_row_len == max_len_kv\n\n    q = torch.randn(B, seq_len_q, nheads_q, head_dim, device=\"cuda\", dtype=dtype)\n    k = torch.randn(B, max_len_kv, nheads_kv, head_dim, device=\"cuda\", dtype=dtype)\n    v = torch.randn(B, max_len_kv, nheads_kv, head_dim, device=\"cuda\", dtype=dtype)\n\n    xq = q.view(1, -1, nheads_q, head_dim)\n    xk = k.view(1, -1, nheads_kv, head_dim).expand(1, -1, nheads_q, -1)\n    xv = v.view(1, -1, nheads_kv, head_dim).expand(1, -1, nheads_q, -1)\n\n    # For non-paged FA3, the seqlens need to be a multiple of tile_size (128) since TMA loading V uses fixed tile-size.\n    if op in [fmha.flash3.FwOp, fmha.flash3.FwOp_KVSplit]:\n        tile_sz = 128\n        seqlens = torch.randint(max_len_kv // tile_sz, size=(B,), device=\"cuda\")\n        seqlens = seqlens * tile_sz\n    else:\n        seqlens = torch.randint(max_len_kv, size=(B,), device=\"cuda\")\n\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[seq_len_q] * B, kv_seqlen=seqlens.tolist(), kv_padding=max_len_kv\n    )\n    if type(attn_bias) not in op.SUPPORTED_ATTN_BIAS_TYPES:\n        pytest.skip(f\"Op {op.NAME} doesn't support {type(attn_bias)}\")\n\n    out_ref, lse_ref = fmha.memory_efficient_attention_forward_requires_grad(\n        xq, xk, xv, attn_bias, op=op\n    )\n\n    # Fill K/V with NaNs at padding positions.\n    mask_uninitialized = (\n        torch.arange(max_len_kv, device=\"cuda\")[None, :].expand(B, max_len_kv)\n        >= seqlens[:, None]\n    )\n    mask_uninitialized = mask_uninitialized[:, :, None, None]\n    k.masked_fill_(mask_uninitialized, float(\"nan\"))\n    v.masked_fill_(mask_uninitialized, float(\"nan\"))\n\n    if op in [fmha.flash3.FwOp, fmha.flash3.FwOp_KVSplit]:\n        # NOTE: FA3 without paged attention uses TMA to load KV from global memory to shared memory based on a fixed length,\n        # which may load NaN elements for variable length cases.\n        # So the current FA3 implementation (TMA KV loading) cannot handle NaNs during V loading, which can propagate NaNs to the output.\n        # NaNs can occur in pre-allocated KV cache initialized with torch.empty()\n        # In these cases, FA3 paged attention should be used instead, which uses cp.async to load KV from global memory to shared memory\n        # based on the variable length (see: https://fburl.com/9q0rsyco)\n        make_paged_kwargs = {\n            \"paged_type\": fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        }\n\n        block_tables = torch.arange(\n            B * padded_per_row_len // page_size, device=\"cuda\", dtype=torch.int32\n        ).reshape(B, -1)\n\n        attn_bias_paged = attn_bias.make_paged(\n            block_tables=block_tables,\n            page_size=page_size,\n            **make_paged_kwargs,  # type: ignore\n        )\n        axq = q.view(1, -1, nheads_q, head_dim)\n        axk_padded = k.view(1, -1, nheads_kv, head_dim).expand(\n            1, -1, nheads_q, head_dim\n        )\n        axv_padded = v.view(1, -1, nheads_kv, head_dim).expand(\n            1, -1, nheads_q, head_dim\n        )\n\n        if type(attn_bias_paged) not in op.SUPPORTED_ATTN_BIAS_TYPES:\n            pytest.skip(f\"Op {op.NAME} doesn't support {type(attn_bias)}\")\n        out, lse = fmha.memory_efficient_attention_forward_requires_grad(\n            axq,\n            axk_padded,\n            axv_padded,\n            attn_bias_paged,\n            op=op,\n        )\n        torch.testing.assert_close(out, out_ref, atol=4e-3, rtol=1e-4)\n\n        torch.testing.assert_close(lse, lse_ref, atol=4e-3, rtol=1e-4)\n    out, lse = fmha.memory_efficient_attention_forward_requires_grad(\n        xq,\n        xk,\n        xv,\n        attn_bias,\n        op=op,\n    )\n\n    torch.testing.assert_close(out, out_ref, atol=4e-3, rtol=1e-4)\n\n    torch.testing.assert_close(lse, lse_ref, atol=4e-3, rtol=1e-4)\n\n\n# end of file\n"
  },
  {
    "path": "tests/test_multiprocessing_utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport os\nfrom typing import List\n\nfrom .multiprocessing_utils import launch_subprocesses\n\n\ndef inner_test(present_parent_keys: List[str] = [], absent_parent_keys: List[str] = []):\n    # each time the process pool submits a job to the child processes, it will also transfer the\n    # environment variables of the parent process to the child process.\n    # we make sure they are available to the child process\n    for parent_key in present_parent_keys:\n        assert parent_key in os.environ\n\n    # if keys are updated in the parent process this should also be reflected on the child process\n    # missing keys should not be available to the child process\n    for parent_key in absent_parent_keys:\n        assert parent_key not in os.environ\n\n    # any inserted local env vars will be removed by our process pool manager at the end of the job\n    # the process pool will restore the original environment variables at the subprocess initialisation\n    assert \"var_temp\" not in os.environ\n\n    # INSERT LOCAL ENV VAR\n    os.environ[\"var_temp\"] = \"1\"\n\n\ndef test_env_vars():\n    # insert global env var\n    os.environ[\"var_1\"] = \"1\"\n    os.environ[\"var_2\"] = \"1\"\n\n    # first job submit => triggers subprocess creation\n    launch_subprocesses(world_size=1, fn=inner_test, present_parent_keys=[\"var_1\"])\n\n    # delete global env var\n    del os.environ[\"var_2\"]\n\n    # insert new global env var\n    os.environ[\"var_3\"] = \"1\"\n\n    # second job submit => reuses the subprocess created before\n    launch_subprocesses(\n        world_size=1,\n        fn=inner_test,\n        present_parent_keys=[\"var_1\", \"var_3\"],\n        absent_parent_keys=[\"var_2\"],\n    )\n"
  },
  {
    "path": "tests/test_profiler.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nfrom contextlib import contextmanager\nfrom typing import cast, Union\n\nimport pytest\nimport torch\nimport torch.nn as nn\n\nimport xformers.ops as xops\nimport xformers.ops.fmha as fmha\nimport xformers.profiler\nfrom torch.nn.attention import sdpa_kernel, SDPBackend\nfrom torch.utils._python_dispatch import _get_current_dispatch_mode\nfrom xformers.profiler import profile_analyzer\n\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\n\n\n# Not using the PyTorch profiler, as it causes segfaults\n# in the CI ~30% of the time\nTEST_SCHEDULE = tuple(\n    x\n    for x in xformers.profiler.api.DEFAULT_SCHEDULE\n    if x[0] is not xformers.profiler.PyTorchProfiler\n)\n\n\n@cuda_only\ndef test_profiler_dispatcher_stream_workaround() -> None:\n    x = torch.zeros([10, 10], device=\"cuda\")\n    with xformers.profiler.profile(\n        \"test_profiler_dispatcher_stream_workaround\", schedule=TEST_SCHEDULE\n    ):\n        for _ in range(20):\n            x.record_stream(torch.cuda.Stream())  # type: ignore\n            xformers.profiler.step()\n\n\n@cuda_only\n@pytest.mark.parametrize(\n    \"device_bs_mm\",\n    [(\"cpu\", 512, 1)]\n    + (\n        [\n            # GPU bound\n            (\"cuda\", 4096, 8),\n            # CPU bound on GPU\n            (\"cuda\", 1, 1),\n        ]\n        if torch.cuda.is_available()\n        else []\n    ),\n)\ndef test_profiler_overhead(device_bs_mm) -> None:\n    PROFILER_MAX_STEPS_OVERHEAD = 30\n\n    device, bs, model_mult = device_bs_mm\n\n    model = torch.nn.Sequential(\n        torch.nn.Linear(1024, 512 * model_mult),\n        torch.nn.Linear(512 * model_mult, 1024),\n    )\n    model.to(device)\n    inp = torch.randn([bs, 1024], device=device)\n    optim = torch.optim.Adam(model.parameters())\n\n    def one_step(model) -> None:\n        model(inp).sum().backward()\n        optim.step()\n        optim.zero_grad()\n\n    # Warmup\n    for _ in range(2):\n        one_step(model)\n\n    # Run with profiler\n    with xformers.profiler.profile(\n        \"test_profiler_overhead\", module=model, schedule=TEST_SCHEDULE\n    ):\n        for _ in range(PROFILER_MAX_STEPS_OVERHEAD):\n            one_step(model)\n\n        assert not model._forward_hooks\n        assert not model._forward_pre_hooks\n        assert not model._backward_hooks\n        assert _get_current_dispatch_mode() is None\n\n    model_opt = torch.compile(model)\n    model_opt_casted = cast(torch.nn.Module, model_opt)\n\n    # Warmup\n    for _ in range(2):\n        one_step(model_opt_casted)\n\n    # Run with profiler\n    with xformers.profiler.profile(\n        \"test_profiler_overhead\", module=model_opt_casted, schedule=TEST_SCHEDULE\n    ):\n        for _ in range(PROFILER_MAX_STEPS_OVERHEAD):\n            one_step(model_opt_casted)\n\n        assert not model_opt_casted._forward_hooks\n        assert not model_opt_casted._forward_pre_hooks\n        assert not model_opt_casted._backward_hooks\n        assert _get_current_dispatch_mode() is None\n\n\n@contextmanager\ndef assert_flops(\n    error_msg: str,\n    *,\n    match: int = -1,\n    at_least: int = -1,\n    at_most: Union[int, float] = math.inf,\n    fw=True,\n    bw=True,\n):\n    try:\n        with torch.profiler.profile(\n            profile_memory=True,\n            record_shapes=True,\n            with_stack=True,\n            with_flops=True,\n            activities=[\n                torch.profiler.ProfilerActivity.CPU,\n                torch.profiler.ProfilerActivity.CUDA,\n            ],\n        ) as p:\n            yield\n    finally:\n        results = profile_analyzer.AnalyzedTrace.from_profile(\n            p.profiler.kineto_results.events()\n        )\n        total_flops = 0.0\n        if fw:\n            total_flops += sum(results.operations_per_dtype_fw.values())\n        if bw:\n            total_flops += sum(results.operations_per_dtype_bw.values())\n        if match != -1:\n            # Some tolerance\n            assert (\n                total_flops * 0.99 < match < total_flops * 1.01\n            ), f\"{error_msg}: {total_flops} flops, expected {match}\"\n        assert total_flops >= at_least, error_msg\n        assert total_flops <= at_most, error_msg\n\n\n@pytest.mark.parametrize(\n    \"dtype\", [torch.float16, torch.float64, torch.float, torch.bfloat16]\n)\n@cuda_only\ndef test_analyze_prof(dtype) -> None:\n    B, N = 64, 128\n    w = torch.empty([128, 128], dtype=dtype, device=\"cuda\", requires_grad=True)\n    x = torch.ones([B, 1, N, 128], dtype=dtype, device=\"cuda\", requires_grad=True)\n    with assert_flops(\"Linear\", match=2 * B * N * 128 * 128):\n        x = x @ w\n    with assert_flops(\"LinearBW\", match=2 * B * N * 128 * 128 * 2, fw=False):\n        x.backward(x)\n\n\n@pytest.mark.parametrize(\"dtype\", [torch.float16])\n@pytest.mark.parametrize(\n    \"backend\",\n    [\n        SDPBackend.EFFICIENT_ATTENTION,\n        SDPBackend.FLASH_ATTENTION,\n        SDPBackend.CUDNN_ATTENTION,\n    ],\n    ids=[\"mem-eff\", \"flash\", \"cudnn\"],\n)\n@pytest.mark.parametrize(\"causal\", [True, False], ids=[\"causal\", \"\"])\n@cuda_only\ndef test_analyze_prof_sdpa(dtype, backend, causal: bool) -> None:\n    B, M, H, K = 64, 1024, 3, 128\n    x = torch.ones([B, H, M, K], dtype=dtype, device=\"cuda\", requires_grad=True)\n    fw_flops = 2 * 2 * M * M * K\n    fw_flops *= B * H\n    if causal:\n        fw_flops //= 2\n    device_sm = torch.cuda.get_device_capability(x.device)\n    if backend in [\n        SDPBackend.CUDNN_ATTENTION,\n        SDPBackend.FLASH_ATTENTION,\n    ] and device_sm not in ((8, 0), (9, 0)):\n        pytest.skip(\"not available\")\n    with sdpa_kernel(backend):\n        with assert_flops(\"SDPA\", match=fw_flops):\n            x = nn.functional.scaled_dot_product_attention(x, x, x, is_causal=causal)\n        with assert_flops(\"SDPA BW\", match=fw_flops * 5 // 2):\n            x.backward(x)\n\n\n@pytest.mark.parametrize(\n    \"op\",\n    [\n        (fmha.cutlass.FwOp, fmha.cutlass.BwOp),\n        (fmha.flash.FwOp, fmha.flash.BwOp),\n    ],\n    ids=[\"cutlass\", \"flash\"],\n)\n@pytest.mark.parametrize(\"causal\", [True, False], ids=[\"causal\", \"\"])\n@cuda_only\ndef test_analyze_prof_memeff(op, causal: bool) -> None:\n    dtype = torch.float16\n    B, M, H, K = 64, 256, 3, 128\n    x = torch.ones([B, M, H, K], dtype=dtype, device=\"cuda\", requires_grad=True)\n    fw_flops = 2 * 2 * M * M * K\n    fw_flops *= B * H\n    bias = None\n    if causal:\n        bias = fmha.attn_bias.LowerTriangularMask()\n        fw_flops //= 2\n    device_sm = torch.cuda.get_device_capability(x.device)\n    if device_sm < op[0].CUDA_MINIMUM_COMPUTE_CAPABILITY:\n        pytest.skip(f\"Requires sm{op[0].CUDA_MINIMUM_COMPUTE_CAPABILITY}\")\n    with assert_flops(\"memory_efficient_attention\", match=fw_flops):\n        y = xops.memory_efficient_attention(x, x, x, attn_bias=bias, op=op)\n    with assert_flops(\"memory_efficient_attention BW\", match=fw_flops * 5 // 2):\n        y.backward(y)\n"
  },
  {
    "path": "tests/test_rmsnorm.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nfrom contextlib import nullcontext\nfrom typing import Optional\n\nimport pytest\nimport torch\nfrom torch import nn\n\nfrom xformers.ops import RMSNorm\n\nfrom .utils import assert_allclose\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\ncuda_sm80_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm80+\"\n)\n\nDTYPES = {\"f16\": torch.float16, \"bf16\": torch.bfloat16, \"f32\": torch.float32}\n\n\nclass RMSNormPytorch(torch.nn.Module):\n    def __init__(self, dim: int, include_weight: bool = True, eps: float = 1e-6):\n        super().__init__()\n        self.eps = eps\n        if include_weight:\n            self.weight: Optional[nn.Parameter] = nn.Parameter(torch.ones(dim))\n        else:\n            self.weight = None\n\n    def _norm(self, x):\n        return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)\n\n    def forward(self, x):\n        output = self._norm(x.float()).type_as(x)\n        if self.weight is not None:\n            output = output * self.weight\n        return output.type_as(x)\n\n\n@cuda_sm80_only\n@pytest.mark.parametrize(\"K\", [273, 4100])\n@pytest.mark.parametrize(\"dtype\", [\"f16\", \"bf16\", \"f32\"])\ndef test_forward(K: int, dtype: str):\n    atol = 1e-8 if dtype == \"f32\" else 1e-4\n    rtol = 1e-5 if dtype == \"f32\" else 0.01\n    torch.manual_seed(1)\n    B, M, K = 31, 27, K\n    device = torch.device(\"cuda\")\n\n    rms_layer = RMSNorm(K).cuda()\n    baseline_layer = RMSNormPytorch(K).cuda()\n    x = torch.rand(B, M, K, device=device, dtype=DTYPES[dtype])\n    torch.nn.init.normal_(rms_layer.weight)  # type: ignore\n    with torch.no_grad():\n        x_rms = rms_layer(x)\n        assert x_rms.shape == x.shape\n        baseline_layer.weight.copy_(rms_layer.weight)  # type: ignore\n    baseline = baseline_layer(x)\n    assert_allclose(x_rms, baseline, atol=atol, rtol=rtol)\n\n    torch.nn.init.ones_(rms_layer.weight)  # type: ignore\n    with torch.no_grad():\n        x_rms1 = rms_layer(x)\n    assert not torch.allclose(x_rms, x_rms1)\n    rms1_layer = RMSNorm(K, include_weight=False)\n    with torch.no_grad():\n        x_rms_1 = rms1_layer(x)\n    assert_allclose(x_rms1, x_rms_1, atol=atol, rtol=rtol)\n\n\n@cuda_sm80_only\n@pytest.mark.parametrize(\"K\", [273, 4100])\n@pytest.mark.parametrize(\"include_weight\", [True, False])\n@pytest.mark.parametrize(\"dtype\", [\"f16\", \"bf16\", \"f32\"])\ndef test_increment(K: int, include_weight: bool, dtype: str):\n    atol = 1e-8 if dtype == \"f32\" else 1e-4\n    rtol = 1e-5 if dtype == \"f32\" else 0.01\n    torch.manual_seed(1)\n    B, M, K = 31, 27, K\n    device = torch.device(\"cuda\")\n    dtype_ = DTYPES[dtype]\n\n    rms_layer = RMSNorm(K, include_weight=include_weight).cuda()\n    x_orig = torch.rand(B, M, K, device=device, dtype=dtype_)\n    y_orig = torch.rand(B, M, K, device=device, dtype=dtype_)\n    x = x_orig.clone()\n    y = y_orig.clone()\n    if include_weight:\n        torch.nn.init.normal_(rms_layer.weight)  # type: ignore\n\n    context = torch.no_grad() if include_weight else nullcontext()\n    with context:  # type: ignore\n        baseline = rms_layer(x_orig + y_orig)\n        out = rms_layer.increment_and_forward_(x, y)\n    assert_allclose(out, baseline, atol=atol, rtol=rtol)\n    assert_allclose(x, x_orig + y_orig, atol=atol, rtol=rtol)\n    assert_allclose(y, y_orig, atol=atol, rtol=rtol)\n"
  },
  {
    "path": "tests/test_rope_padded.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nfrom functools import partial\nfrom typing import Optional\n\nimport pytest\nimport torch\n\nfrom xformers.ops import rope_padded\nfrom xformers.ops.fmha.attn_bias import BlockDiagonalCausalWithOffsetPaddedKeysMask\n\nfrom .utils import assert_allclose\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\ncuda_sm80_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm80+\"\n)\n\n\ndef apply_scaling(\n    freqs: torch.Tensor,\n    old_context_len: float,\n    low_freq_factor: float,\n    high_freq_factor: float,\n    dynamic_scale_factor: float,\n):\n    low_freq_wavelen = old_context_len / low_freq_factor\n    high_freq_wavelen = old_context_len / high_freq_factor\n    assert low_freq_wavelen >= high_freq_wavelen\n\n    for idx, freq in enumerate(freqs):\n        wavelen = 2 * math.pi / freq\n        if wavelen > low_freq_wavelen:\n            freqs[idx] = freq / dynamic_scale_factor\n\n        if high_freq_wavelen <= wavelen and wavelen <= low_freq_wavelen:\n            assert low_freq_wavelen != high_freq_wavelen\n            smooth = (old_context_len / wavelen - low_freq_factor) / (\n                high_freq_factor - low_freq_factor\n            )\n            freqs[idx] = (1 - smooth) * freqs[\n                idx\n            ] / dynamic_scale_factor + smooth * freqs[idx]\n    return freqs\n\n\ndef _slow_rope(\n    x: torch.Tensor,\n    *,\n    seqpos: Optional[torch.Tensor] = None,\n    theta=10000,\n    linear_scale=1,\n    adjacents: bool = True,\n    use_dynamic_scaling: bool = False,\n    dynamic_old_context_len: float = 8192.0,\n    dynamic_scale_factor: float = 16.0,\n    dynamic_low_freq_factor: float = 1.0,\n    dynamic_high_freq_factor: float = 32.0,\n):\n    \"\"\"\n    Simple rope calculation of rope of one tensor\n\n    Args:\n        x: input, shape (B, M, H, K).\n        seqpos: gives the position of each sequence element in x in its sequence\n            (shape (M,)).\n    \"\"\"\n    x_shape = x.shape\n    dim = x_shape[-1]\n    seq_dim = 1\n    M = x_shape[seq_dim]\n    assert dim % 2 == 0\n    if seqpos is None:\n        seqpos = torch.arange(M, device=x.device)\n    power = torch.arange(0, dim, 2, device=x.device)[: (dim // 2)].float() / dim\n    freqs: torch.Tensor = 1.0 / (theta**power)  # type: ignore\n    if use_dynamic_scaling:\n        freqs = apply_scaling(\n            freqs,\n            dynamic_old_context_len,\n            dynamic_low_freq_factor,\n            dynamic_high_freq_factor,\n            dynamic_scale_factor,\n        )\n    all_freqs = torch.outer(seqpos / linear_scale, freqs)\n    freqs_cis = torch.polar(torch.ones_like(all_freqs), all_freqs)  # complex64\n    for _ in range(x.ndim - seq_dim - 2):\n        freqs_cis = freqs_cis[:, None]\n    if adjacents:\n        x_reshaped = x.float().unflatten(-1, (-1, 2))\n        x_ = torch.view_as_complex(x_reshaped)\n        x_out = torch.view_as_real(x_ * freqs_cis)\n    else:\n        x_reshaped = x.float().unflatten(-1, (2, -1)).transpose(-1, -2).contiguous()\n        x_ = torch.view_as_complex(x_reshaped)\n        x_out = torch.view_as_real(x_ * freqs_cis)\n        x_out = x_out.transpose(-1, -2)\n    return x_out.flatten(-2).type_as(x)\n\n\ndef _slow_rope2(\n    x: torch.Tensor,\n    *,\n    seqpos: Optional[torch.Tensor] = None,\n    theta=10000,\n    linear_scale=1,\n    adjacents: bool = True,\n):\n    \"\"\"\n    More flexible unused version of _slow_rope\n    - allows varying dtypes.\n    \"\"\"\n    internal_dtype = torch.float64\n    dim = x.shape[-1]\n    seq_dim = 1\n    M = x.shape[seq_dim]\n    assert dim % 2 == 0\n    if seqpos is None:\n        seqpos = torch.arange(M, device=x.device)\n    power = (\n        torch.arange(0, dim, 2, device=x.device)[: (dim // 2)].to(internal_dtype) / dim\n    )\n    # freqs = 1.0 / (theta**power)\n    freqs = theta**-power\n    f = torch.outer(seqpos / linear_scale, freqs)\n    for _ in range(x.ndim - seq_dim - 2):\n        f = f[:, None]\n    if adjacents:\n        x1, x2 = x.to(internal_dtype).unflatten(-1, (-1, 2)).unbind(-1)\n        y1 = x1 * f.cos() - x2 * f.sin()\n        y2 = x1 * f.sin() + x2 * f.cos()\n        x_out = torch.stack([y1, y2], -1)\n    else:\n        x1, x2 = x.to(internal_dtype).unflatten(-1, (2, -1)).unbind(-2)\n        y1 = x1 * f.cos() - x2 * f.sin()\n        y2 = x1 * f.sin() + x2 * f.cos()\n        x_out = torch.stack([y1, y2], -2)\n    return x_out.flatten(-2).type_as(x)\n\n\nDTYPES = {\"bf16\": torch.bfloat16, \"f32\": torch.float32}\nROPE_ATOL_RTOL = {\n    \"bf16\": (5e-3, 8e-3),\n    \"f32\": (5e-3, 1e-5),\n}\n\n\n@cuda_sm80_only\n@pytest.mark.parametrize(\n    \"adjacents\", [True, False], ids=lambda x: \"adj\" if x else \"non-adj\"\n)\n@pytest.mark.parametrize(\"dtype_str\", [\"bf16\", \"f32\"])\n@pytest.mark.parametrize(\"internal_dtype\", [\"\", \"f32\", \"f64\"])\n@pytest.mark.parametrize(\"dim\", [100, 4098])\n@pytest.mark.parametrize(\"padding\", [87, 18300])\n@pytest.mark.parametrize(\"groups\", [1, 3])\n@pytest.mark.parametrize(\n    \"linear_scale, use_dynamic_scaling\", [(1.0, False), (4.0, False), (1.0, True)]\n)\ndef test_consistency(\n    adjacents: bool,\n    dim: int,\n    padding: int,\n    groups: int,\n    internal_dtype: str,\n    dtype_str: str,\n    linear_scale: float,\n    use_dynamic_scaling: bool,\n):\n    torch.manual_seed(1)\n    heads, kvheads = 10, 2\n    nqueries = [2, 1, 1]\n    cache_lens = [27, padding - 5, padding // 2]\n    device = torch.device(\"cuda\")\n    dtype = DTYPES[dtype_str]\n\n    # Can we make the internals of attn_bias be on the gpu.\n    attn_bias = BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=nqueries, kv_padding=padding, kv_seqlen=cache_lens\n    )\n\n    total_cache_length = len(cache_lens) * padding\n    total_nqueries = sum(nqueries)\n    if groups == 1:\n        cache_k = torch.rand(\n            1, total_cache_length, kvheads, dim, device=device, dtype=dtype\n        )\n        cache_v = torch.rand(\n            1, total_cache_length, kvheads, dim, device=device, dtype=dtype\n        )\n        xq = torch.rand(1, total_nqueries, heads, dim, device=device, dtype=dtype)\n        xk = torch.rand(1, total_nqueries, kvheads, dim, device=device, dtype=dtype)\n        xv = torch.rand(1, total_nqueries, kvheads, dim, device=device, dtype=dtype)\n    else:\n        cache_k = torch.rand(\n            1, total_cache_length, groups, kvheads, dim, device=device, dtype=dtype\n        )\n        cache_v = torch.rand(\n            1, total_cache_length, groups, kvheads, dim, device=device, dtype=dtype\n        )\n        xq = torch.rand(\n            1, total_nqueries, groups, heads, dim, device=device, dtype=dtype\n        )\n        xk = torch.rand(\n            1, total_nqueries, groups, kvheads, dim, device=device, dtype=dtype\n        )\n        xv = torch.rand(\n            1, total_nqueries, groups, kvheads, dim, device=device, dtype=dtype\n        )\n\n    cache_k_orig = cache_k.clone()\n    cache_v_orig = cache_v.clone()\n    out = rope_padded(\n        xq,\n        xk,\n        xv,\n        cache_k,\n        cache_v,\n        attn_bias,\n        linear_scale=linear_scale,\n        adjacents=adjacents,\n        internal_dtype=internal_dtype,\n        use_dynamic_scaling=use_dynamic_scaling,\n    )\n\n    seqpos = torch.tensor(\n        [cache_lens[0] - 2, cache_lens[0] - 1, cache_lens[1] - 1, cache_lens[2] - 1],\n        device=device,\n    )\n    cache_locs = [seqpos[0], seqpos[1], padding + seqpos[2], 2 * padding + seqpos[3]]\n    baseline = _slow_rope if dtype_str == \"f32\" else _slow_rope2\n    if use_dynamic_scaling:\n        baseline = partial(_slow_rope, use_dynamic_scaling=True)  # type: ignore\n    expected_out = baseline(  # type: ignore\n        xq, linear_scale=linear_scale, seqpos=seqpos, adjacents=adjacents\n    )\n    atol, rtol = ROPE_ATOL_RTOL[dtype_str]\n    assert_allclose(out, expected_out, atol=atol, rtol=rtol)\n\n    assert_allclose(cache_v[:, cache_locs], xv, atol=atol, rtol=rtol)\n    cache_v[:, cache_locs] = cache_v_orig[:, cache_locs]\n    assert torch.allclose(cache_v, cache_v_orig)\n\n    slow_roped_xk = _slow_rope(\n        xk,\n        linear_scale=linear_scale,\n        seqpos=seqpos,\n        adjacents=adjacents,\n        use_dynamic_scaling=use_dynamic_scaling,\n    )\n    assert_allclose(\n        cache_k[:, cache_locs],\n        slow_roped_xk,\n        atol=atol,\n        rtol=rtol,\n    )\n    cache_k[:, cache_locs] = cache_k_orig[:, cache_locs]\n    assert torch.allclose(cache_k, cache_k_orig)\n\n\n@cuda_sm80_only\n@pytest.mark.parametrize(\"seqlen\", [512, 2**16])\ndef test_rope_prefill(seqlen) -> None:\n    heads, kvheads = 2, 1\n    dim = 32\n    device = \"cuda\"\n    adjacents = True\n    dtype = torch.bfloat16\n\n    attn_bias = BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[seqlen], kv_padding=seqlen + 1, kv_seqlen=[seqlen]\n    )\n    cache_k = torch.rand(1, seqlen + 1, kvheads, dim, device=device, dtype=dtype)\n    cache_v = torch.randn_like(cache_k)\n    xq = torch.rand(1, seqlen, heads, dim, device=device, dtype=dtype)\n    xk = torch.rand(1, seqlen, kvheads, dim, device=device, dtype=dtype)\n    xv = torch.rand(1, seqlen, kvheads, dim, device=device, dtype=dtype)\n\n    out = rope_padded(\n        xq,\n        xk,\n        xv,\n        cache_k,\n        cache_v,\n        attn_bias,\n        adjacents=adjacents,\n    )\n\n    seqpos = torch.arange(start=0, end=seqlen, device=device)\n    expected_out = _slow_rope2(xq, seqpos=seqpos, adjacents=adjacents)\n    atol, rtol = ROPE_ATOL_RTOL[\"bf16\"]\n    assert_allclose(out, expected_out, atol=atol, rtol=rtol)\n\n\n@cuda_sm80_only\ndef test_rope_seqpos() -> None:\n    heads, kvheads = 2, 1\n    dim = 32\n    device = \"cuda\"\n    adjacents = True\n    dtype = torch.bfloat16\n    seqlen = 723\n\n    attn_bias = BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[seqlen], kv_padding=seqlen + 1, kv_seqlen=[seqlen]\n    )\n    cache_k = torch.rand(1, seqlen + 1, kvheads, dim, device=device, dtype=dtype)\n    cache_v = torch.randn_like(cache_k)\n    xq = torch.rand(1, seqlen, heads, dim, device=device, dtype=dtype)\n    xk = torch.rand(1, seqlen, kvheads, dim, device=device, dtype=dtype)\n    xv = torch.rand(1, seqlen, kvheads, dim, device=device, dtype=dtype)\n\n    def inner(seqpos, *, first_seqpos_input=None, seqpos_input=None):\n        out = rope_padded(\n            xq,\n            xk,\n            xv,\n            cache_k,\n            cache_v,\n            attn_bias,\n            adjacents=adjacents,\n            first_seqpos=first_seqpos_input,\n            seqpos=seqpos_input,\n        )\n\n        expected_out = _slow_rope2(xq, seqpos=seqpos, adjacents=adjacents)\n        atol, rtol = ROPE_ATOL_RTOL[\"bf16\"]\n        assert_allclose(out, expected_out, atol=atol, rtol=rtol)\n\n    inner(torch.arange(start=0, end=seqlen, device=device))\n    inner(\n        torch.arange(start=4, end=seqlen + 4, device=device),\n        first_seqpos_input=torch.tensor([4], device=device),\n    )\n    custom_seqpos = torch.arange(start=0, end=seqlen, device=device)\n    custom_seqpos[231] = 934\n    custom_seqpos[423] = 134\n    inner(custom_seqpos, seqpos_input=custom_seqpos)\n"
  },
  {
    "path": "tests/test_seqpar.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport os\nimport random\nfrom typing import Tuple\n\nimport pytest\nimport torch\n\nfrom xformers.ops import (\n    sequence_parallel_leading_matmul,\n    sequence_parallel_trailing_matmul,\n)\n\nfrom .multiprocessing_utils import launch_subprocesses\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\ncuda_sm80_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm70+\"\n)\nat_least_2_gpus = pytest.mark.skipif(\n    torch.cuda.device_count() < 2, reason=\"needs at least 2 GPUs\"\n)\n\n\ndef reference_leading(input_, w1, w2):\n    hidden1 = torch.matmul(input_, w1.t())\n    hidden2 = torch.matmul(input_, w2.t())\n    return [hidden1, hidden2]\n\n\ndef reference_trailing(hidden, w):\n    output = torch.matmul(hidden, w.t())\n    return output\n\n\ndef xformers_leading(input_, w1, w2, *, fuse, group):\n    return sequence_parallel_leading_matmul(\n        input_, [w1.t(), w2.t()], fuse=fuse, process_group=group\n    )\n\n\ndef xformers_trailing(hidden, w, *, fuse, group):\n    return sequence_parallel_trailing_matmul(\n        hidden, w.t(), fuse=fuse, process_group=group\n    )\n\n\ndef inner_seqpar(\n    kind: str,\n    step: str,\n    dims: Tuple[int, ...],\n    dtype: torch.dtype,\n    compile: bool,\n    seed: int,\n):\n    os.environ[\"TORCH_SYMM_MEM_ALLOW_OVERLAPPING_DEVICES\"] = \"1\"\n\n    my_rank = torch.distributed.get_rank()\n    world_size = torch.distributed.get_world_size()\n    subgroup = torch.distributed.new_group()\n\n    fused = True\n    if kind == \"unfused\":\n        fused = False\n    elif kind == \"fallback\":\n        os.environ[\"DISABLE_FUSED_SEQUENCE_PARALLEL\"] = \"1\"\n\n    torch.random.manual_seed(seed)\n    torch._dynamo.reset_code_caches()  # avoids hitting recompilation limit\n\n    batch_dims = dims[:-2]\n    outer_dim = dims[-2]\n    inner_dim = dims[-1]\n\n    # To check for correctness we want to compare the outputs but the accuracy\n    # of matmuls, apparently, is not that great. We thus try to produce inputs\n    # for which no rounding at all will occur. We do this by using zero or one\n    # inputs, so their product will also be zero or one, and keep the reduction\n    # dimension small enough so that they fit in the mantissa without overflow.\n    max_exact_value = 2 * (1 / torch.finfo(dtype).eps)\n    # 0.25 is the ratio of expected ones and we aim at 2/3 of the safe range\n    assert outer_dim * 0.25 <= max_exact_value * 0.66\n    assert inner_dim * world_size * 0.25 <= max_exact_value * 0.66\n\n    def my_chunk(t, *, dim):\n        return t.tensor_split(world_size, dim=dim)[my_rank]\n\n    if step == \"leading\":\n        input_ = torch.testing.make_tensor(\n            batch_dims + (outer_dim,),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n        weight1, weight2 = [\n            torch.testing.make_tensor(\n                (inner_dim * (idx + 1), outer_dim),\n                dtype=dtype,\n                device=\"cuda\",\n                low=0,\n                high=1,\n            ).round()\n            for idx in range(2)\n        ]\n        gradient1, gradient2 = [\n            torch.testing.make_tensor(\n                batch_dims + (inner_dim * (idx + 1),),\n                dtype=dtype,\n                device=\"cuda\",\n                low=0,\n                high=1,\n            ).round()\n            for idx in range(2)\n        ]\n\n        # Non-fused reference code\n        input_ref = input_.detach().requires_grad_()\n        weight1_ref = weight1.detach().requires_grad_()\n        weight2_ref = weight2.detach().requires_grad_()\n\n        output1_ref, output2_ref = reference_leading(\n            input_ref, weight1_ref, weight2_ref\n        )\n        torch.autograd.backward([output1_ref, output2_ref], [gradient1, gradient2])\n\n        my_output1_ref = my_chunk(output1_ref, dim=-1)\n        my_output2_ref = my_chunk(output2_ref, dim=-1)\n        my_weight1_grad_ref = my_chunk(weight1_ref.grad, dim=0)\n        my_weight2_grad_ref = my_chunk(weight2_ref.grad, dim=0)\n        my_input_grad_ref = my_chunk(input_ref.grad, dim=0)\n\n        # Faster fused mode\n        my_input_xf = my_chunk(input_, dim=0).detach().requires_grad_()\n        my_weight1_xf = my_chunk(weight1, dim=0).detach().requires_grad_()\n        my_weight2_xf = my_chunk(weight2, dim=0).detach().requires_grad_()\n        my_gradient1 = my_chunk(gradient1, dim=-1)\n        my_gradient2 = my_chunk(gradient2, dim=-1)\n\n        my_output1_xf, my_output2_xf = torch.compile(\n            xformers_leading, fullgraph=True, disable=not compile\n        )(my_input_xf, my_weight1_xf, my_weight2_xf, fuse=fused, group=subgroup)\n        torch.autograd.backward(\n            [my_output1_xf, my_output2_xf], [my_gradient1, my_gradient2]\n        )\n\n        my_weight1_grad_xf = my_weight1_xf.grad\n        my_weight2_grad_xf = my_weight2_xf.grad\n        my_input_grad_xf = my_input_xf.grad\n\n        # Checks\n        torch.testing.assert_close(my_output1_ref, my_output1_xf)\n        torch.testing.assert_close(my_output2_ref, my_output2_xf)\n        torch.testing.assert_close(my_input_grad_ref, my_input_grad_xf)\n        torch.testing.assert_close(my_weight1_grad_ref, my_weight1_grad_xf)\n        torch.testing.assert_close(my_weight2_grad_ref, my_weight2_grad_xf)\n\n        torch.library.opcheck(\n            torch.ops.xformers_python.sequence_parallel_leading_matmul_fwd,\n            (my_input_xf.flatten(0, -2), [my_weight1_xf.t(), my_weight2_xf.t()]),\n            {\"fuse\": fused, \"process_group_name\": subgroup.group_name},\n        )\n\n    elif step == \"trailing\":\n        input_ = torch.testing.make_tensor(\n            batch_dims + (inner_dim,),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n        weight = torch.testing.make_tensor(\n            (outer_dim, inner_dim),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n        gradient = torch.testing.make_tensor(\n            batch_dims + (outer_dim,),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n\n        # Non-fused reference code\n        input_ref = input_.detach().requires_grad_()\n        weight_ref = weight.detach().requires_grad_()\n\n        output_ref = reference_trailing(input_ref, weight_ref)\n        torch.autograd.backward([output_ref], [gradient])\n\n        my_output_ref = my_chunk(output_ref, dim=0)\n        my_weight_grad_ref = my_chunk(weight_ref.grad, dim=1)\n        my_input_grad_ref = my_chunk(input_ref.grad, dim=-1)\n\n        # Faster fused mode\n        my_input_xf = my_chunk(input_, dim=-1).detach().clone().requires_grad_()\n        my_weight_xf = my_chunk(weight, dim=1).detach().requires_grad_()\n        my_gradient = my_chunk(gradient, dim=0)\n\n        my_output_xf = torch.compile(\n            xformers_trailing, fullgraph=True, disable=not compile\n        )(my_input_xf, my_weight_xf, fuse=fused, group=subgroup)\n        torch.autograd.backward([my_output_xf], [my_gradient])\n\n        my_weight_grad_xf = my_weight_xf.grad\n        my_input_grad_xf = my_input_xf.grad\n\n        # Checks\n        torch.testing.assert_close(my_output_ref, my_output_xf)\n        torch.testing.assert_close(my_input_grad_ref, my_input_grad_xf)\n        torch.testing.assert_close(my_weight_grad_ref, my_weight_grad_xf)\n\n        torch.library.opcheck(\n            torch.ops.xformers_python.sequence_parallel_trailing_matmul_fwd,\n            (my_input_xf.flatten(0, -2), my_weight_xf.t()),\n            {\"fuse\": fused, \"process_group_name\": subgroup.group_name},\n        )\n\n\n# PyTorch doesn't support pre-sm80 for its signaling kernels\n# https://github.com/pytorch/pytorch/pull/146308\n@cuda_sm80_only\n@pytest.mark.parametrize(\n    \"kind\",\n    [\n        \"singleton\",\n        pytest.param(\"unfused\", marks=at_least_2_gpus),\n        pytest.param(\"fallback\", marks=at_least_2_gpus),\n        \"fused\",\n    ],\n)\n@pytest.mark.parametrize(\n    \"step\",\n    [\n        \"leading\",\n        \"trailing\",\n    ],\n)\n@pytest.mark.parametrize(\n    \"dims\",\n    [\n        pytest.param((2, 2, 512, 512, 256), id=\"nice-shapes\"),\n        pytest.param((2, 1023, 511, 257), id=\"ugly-shapes\"),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        pytest.param(torch.bfloat16, id=\"bf16\"),\n        pytest.param(torch.float16, id=\"fp16\"),\n        pytest.param(torch.float32, id=\"fp32\"),\n    ],\n)\n@pytest.mark.parametrize(\n    \"compile\", [pytest.param(False, id=\"eager\"), pytest.param(True, id=\"compile\")]\n)\ndef test_seqpar(\n    kind: str,\n    step: str,\n    dims: Tuple[int, ...],\n    dtype: torch.dtype,\n    compile: bool,\n):\n    if compile and dtype is torch.bfloat16 and compute_capability < (8, 0):\n        # https://fb.workplace.com/groups/1075192433118967/posts/1480158559289017\n        pytest.skip(\"Dynamo misbehaves on V100 or earlier when handling bf16\")\n\n    world_size = 1 if kind == \"singleton\" else 2\n    seed = random.getrandbits(32)\n    launch_subprocesses(\n        world_size=world_size,\n        fn=inner_seqpar,\n        kind=kind,\n        step=step,\n        dims=dims,\n        dtype=dtype,\n        compile=compile,\n        seed=seed,\n    )\n"
  },
  {
    "path": "tests/test_sequence_parallel_fused_ops.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport os\nimport random\nfrom typing import Tuple\n\nimport pytest\nimport torch\n\nfrom xformers.ops import fused_allgather_and_linear, fused_linear_and_reducescatter\n\nfrom .multiprocessing_utils import launch_subprocesses\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\ncuda_sm80_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm80+\"\n)\nat_least_2_gpus = pytest.mark.skipif(\n    torch.cuda.device_count() < 2, reason=\"needs at least 2 GPUs\"\n)\n\n\ndef compare_fused_and_non_fused_ops(\n    my_rank: int,\n    world_size: int,\n    subgroup: torch.distributed.ProcessGroup,\n    step: str,\n    dims: Tuple[int, ...],\n    dtype: torch.dtype,\n    compile: bool,\n):\n    os.environ[\"TORCH_SYMM_MEM_ALLOW_OVERLAPPING_DEVICES\"] = \"1\"\n\n    batch_dims = dims[:-2]\n    subbatch_dims = (batch_dims[0] // world_size,) + batch_dims[1:]\n    outer_dim = dims[-2]\n    inner_dim = dims[-1]\n\n    # To check for correctness we want to compare the outputs but the accuracy\n    # of matmuls, apparently, is not that great. We thus try to produce inputs\n    # for which no rounding at all will occur. We do this by using zero or one\n    # inputs, so their product will also be zero or one, and keep the reduction\n    # dimension small enough so that they fit in the mantissa without overflow.\n    max_exact_value = 2 * (1 / torch.finfo(dtype).eps)\n    # 0.25 is the ratio of expected ones and we aim at 2/3 of the safe range\n    assert outer_dim * 0.25 <= max_exact_value * 0.66\n    assert inner_dim * world_size * 0.25 <= max_exact_value * 0.66\n\n    if step == \"all-gather\":\n        inputs = torch.testing.make_tensor(\n            (world_size,) + subbatch_dims + (outer_dim,),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n        weight = torch.testing.make_tensor(\n            (inner_dim, outer_dim), dtype=dtype, device=\"cuda\", low=0, high=1\n        ).round()\n\n        # Non-fused reference code\n        output_reference = torch.matmul(inputs, weight.t()).flatten(0, 1)\n\n        # Faster fused mode\n        fused_allgather_and_linear_compiled = torch.compile(\n            fused_allgather_and_linear, fullgraph=True, disable=not compile\n        )\n        output_fused = fused_allgather_and_linear_compiled(\n            inputs[my_rank], weight, group=subgroup\n        )\n\n    elif step == \"reduce-scatter\":\n        inputs = torch.testing.make_tensor(\n            (world_size,) + batch_dims + (inner_dim,),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n        weights = torch.testing.make_tensor(\n            (world_size, outer_dim, inner_dim),\n            dtype=dtype,\n            device=\"cuda\",\n            low=0,\n            high=1,\n        ).round()\n\n        # Non-fused reference code\n        staging = torch.empty(\n            (world_size,) + subbatch_dims + (outer_dim,), dtype=dtype, device=\"cuda\"\n        )\n        for rank in range(world_size):\n            torch.matmul(\n                inputs[rank].tensor_split(world_size, dim=0)[my_rank],\n                weights[rank].t(),\n                out=staging[rank],\n            )\n        output_reference = torch.sum(staging, dim=0, dtype=dtype)\n\n        # Faster fused mode\n        fused_linear_and_reducescatter_compiled = torch.compile(\n            fused_linear_and_reducescatter, fullgraph=True, disable=not compile\n        )\n        output_fused = fused_linear_and_reducescatter_compiled(\n            inputs[my_rank], weights[my_rank], group=subgroup\n        )\n\n    torch.testing.assert_close(output_reference, output_fused, atol=0, rtol=0)\n\n\ndef inner_sequence_parallel_fused(\n    seed: int,\n    kind: str,\n    step: str,\n    dims: Tuple[int, ...],\n    dtype: torch.dtype,\n    use_compile: bool,\n):\n    my_rank = torch.distributed.get_rank()\n    world_size = torch.distributed.get_world_size()\n    subgroup = torch.distributed.new_group()\n\n    if kind == \"fallback\":\n        os.environ[\"DISABLE_FUSED_SEQUENCE_PARALLEL\"] = \"1\"\n\n    torch.random.manual_seed(seed)\n    if use_compile:\n        # https://fb.workplace.com/groups/1075192433118967/permalink/1471157400189133/\n        # Dynamo doesn't know how to treat ProcessGroup symbolically, so it specializes\n        # on that particular ProcessGroup and will recompile if you pass a different one\n        torch._dynamo.reset_code_caches()  # avoids hitting recompilation limit\n\n    compare_fused_and_non_fused_ops(\n        my_rank=my_rank,\n        world_size=world_size,\n        subgroup=subgroup,\n        step=step,\n        dims=dims,\n        dtype=dtype,\n        compile=use_compile,\n    )\n\n\n# PyTorch doesn't support pre-sm80 for its signaling kernels\n# https://github.com/pytorch/pytorch/pull/146308\n@cuda_sm80_only\n@pytest.mark.parametrize(\n    \"kind\",\n    [\"singleton\", pytest.param(\"fallback\", marks=at_least_2_gpus), \"pytorch\"],\n)\n@pytest.mark.parametrize(\"step\", [\"all-gather\", \"reduce-scatter\"])\n@pytest.mark.parametrize(\n    \"dims\",\n    [\n        pytest.param((2, 2, 512, 512, 256), id=\"nice-shapes\"),\n        pytest.param((2, 1023, 511, 257), id=\"ugly-shapes\"),\n    ],\n)\n@pytest.mark.parametrize(\n    \"dtype\",\n    [\n        # https://fb.workplace.com/groups/1075192433118967/posts/1480158559289017\n        # Dynamo misbehaves on V100 or earlier when handling bf16\n        pytest.param(torch.bfloat16, id=\"bf16\", marks=cuda_sm80_only),\n        pytest.param(torch.float16, id=\"fp16\"),\n        pytest.param(torch.float32, id=\"fp32\"),\n    ],\n)\n@pytest.mark.parametrize(\n    \"use_compile\", [pytest.param(False, id=\"eager\"), pytest.param(True, id=\"compile\")]\n)\ndef test_sequence_parallel_fused(\n    kind: str, step: str, dims: Tuple[int, ...], dtype: torch.dtype, use_compile: bool\n):\n    world_size = 1 if kind == \"singleton\" else 2\n    seed = random.getrandbits(32)\n    launch_subprocesses(\n        world_size,\n        inner_sequence_parallel_fused,\n        seed=seed,\n        kind=kind,\n        step=step,\n        dims=dims,\n        dtype=dtype,\n        use_compile=use_compile,\n    )\n\n\ndef inner_sequence_parallel_fused_handle_all_dtypes(\n    seed: int,\n    step: str,\n    dims: Tuple[int, ...],\n):\n    my_rank = torch.distributed.get_rank()\n    world_size = torch.distributed.get_world_size()\n    subgroup = torch.distributed.new_group()\n\n    torch.random.manual_seed(seed)\n\n    for dtype in [torch.bfloat16, torch.float16, torch.float32]:\n        compare_fused_and_non_fused_ops(\n            my_rank=my_rank,\n            world_size=world_size,\n            subgroup=subgroup,\n            step=step,\n            dims=dims,\n            dtype=dtype,\n            compile=False,\n        )\n\n\n# PyTorch doesn't support pre-sm80 for its signaling kernels\n# https://github.com/pytorch/pytorch/pull/146308\n@cuda_sm80_only\n@pytest.mark.parametrize(\"step\", [\"all-gather\", \"reduce-scatter\"])\n@pytest.mark.parametrize(\n    \"dims\",\n    [\n        pytest.param((2, 2, 512, 512, 256), id=\"nice-shapes\"),\n        pytest.param((2, 1023, 511, 257), id=\"ugly-shapes\"),\n    ],\n)\ndef test_sequence_parallel_fused_handle_all_dtypes(\n    step: str,\n    dims: Tuple[int, ...],\n):\n    world_size = 2\n    seed = random.getrandbits(32)\n    launch_subprocesses(\n        world_size,\n        inner_sequence_parallel_fused_handle_all_dtypes,\n        seed=seed,\n        step=step,\n        dims=dims,\n    )\n"
  },
  {
    "path": "tests/test_sparse_tensors.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport pytest\nimport torch\n\n# needed to register custom ops\nimport xformers  # noqa: F401\nfrom xformers.ops import masked_matmul\nfrom xformers.sparse import BlockSparseTensor\n\nfrom .utils import disable_tf32\n\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\n_devices = (\n    [\"cpu\", \"cuda:0\"] if torch.cuda.is_available() and torch.version.cuda else [\"cpu\"]\n)\n_tensor_types = [BlockSparseTensor]\n\n\ndef _create_blocksparse_tensor(\n    device, block_size=32, Z=8, C=2, H=64, W=64, dtype=torch.float32\n):\n    layout = torch.randint(2, (C, H // block_size, W // block_size), device=device)\n    layout[:, :, 0] = 1\n    layout[:, 0, :] = 1\n    values = torch.randn(Z, layout.sum(), block_size, block_size, device=device).to(\n        dtype\n    )\n\n    return BlockSparseTensor(values, layout)\n\n\ndef _create_tensor(tensor_type, device, dtype, shape, sparsity):\n    if tensor_type == BlockSparseTensor:\n        block_size = 16\n        return _create_blocksparse_tensor(\n            device=device, dtype=dtype, block_size=block_size\n        )\n\n\ndef _seed():\n    torch.random.manual_seed(42)\n    torch.cuda.manual_seed_all(42)\n\n\ndef _get_dtype_atol(tensor_type, device: str):\n    _seed()\n\n    if tensor_type == BlockSparseTensor and \"cuda\" in device:\n        # Upstream GPU blocksparse (Triton op) uses TF32 by default for all internal computations\n        # TF32 has the precision of fp16 but the range of fp32\n        # See https://blogs.nvidia.com/blog/2020/05/14/tensorfloat-32-precision-format/\n        torch.backends.cuda.matmul.allow_tf32 = True\n        torch.backends.cudnn.allow_tf32 = True  # type: ignore\n        return torch.float32, 1e-1\n\n    # Force pytorch to keep its computations as float32 (will default to tf32 with recent cuda and ampere+ GPU)\n    torch.backends.cuda.matmul.allow_tf32 = False\n    torch.backends.cudnn.allow_tf32 = False  # type: ignore\n\n    return torch.float32, 1e-5\n\n\n@disable_tf32\n@pytest.mark.parametrize(\"tensor_type\", _tensor_types)\n@pytest.mark.parametrize(\"device\", _devices)\ndef test_masked_matmul(tensor_type, device):\n    N, C, H, W, L = 8, 2, 64, 64, 32\n    sparsity = 0.7\n    dtype, atol = _get_dtype_atol(tensor_type, device)\n\n    shape0 = (N, C, H, W)\n    shape1 = (N, C, H, L)\n    shape2 = (N, C, W, L)\n\n    if tensor_type != BlockSparseTensor:\n        shape0 = shape0[1:]\n        shape1 = shape1[1:]\n        shape2 = shape2[1:]\n\n    mask_sparse = _create_tensor(\n        tensor_type, device, dtype=torch.bool, shape=shape0, sparsity=sparsity\n    )\n    mask = mask_sparse.to_dense()\n\n    a = torch.randn(shape1, device=device, dtype=dtype)\n    b = torch.randn(shape2, device=device, dtype=dtype)\n\n    aa = a.clone()\n    bb = b.clone()\n\n    a.requires_grad_(True)\n    b.requires_grad_(True)\n    aa.requires_grad_(True)\n    bb.requires_grad_(True)\n\n    bt = b.transpose(-2, -1)\n    bbt = bb.transpose(-2, -1)\n\n    res_gt = masked_matmul(a, bt, mask)\n    res = masked_matmul(aa, bbt, mask_sparse)\n\n    res_dense = res.to_dense()\n    res_dense = torch.where(mask, res_dense, torch.full_like(res_dense, float(\"-inf\")))\n\n    assert res.dtype == res_gt.dtype\n    assert torch.allclose(res_dense, res_gt, atol=atol)\n\n    # try to workaround non-contiguous issues with triton for now\n    res_gt.backward(torch.ones_like(res_gt))\n    res.values().backward(torch.ones_like(res.values()))\n\n    assert torch.allclose(a.grad, aa.grad, atol=atol)\n    assert torch.allclose(b.grad, bb.grad, atol=atol)\n\n\n@disable_tf32\n@pytest.mark.parametrize(\"tensor_type\", _tensor_types)\n@pytest.mark.parametrize(\"device\", _devices)\ndef test_bmm(tensor_type, device):\n    N, C, H, W, L = 8, 2, 64, 64, 32\n    dtype, atol = _get_dtype_atol(tensor_type, device)\n\n    sparsity = 0.8\n    shape0 = (N, C, H, W)\n    shape1 = (N, C, W, L)\n\n    if tensor_type != BlockSparseTensor:\n        shape0 = shape0[1:]\n        shape1 = shape1[1:]\n\n    a_sparse = _create_tensor(\n        tensor_type, device, dtype=dtype, shape=shape0, sparsity=sparsity\n    )\n    a = a_sparse.to_dense()\n    mask = a != 0\n\n    a_sparse.requires_grad_(True)\n    a.requires_grad_(True)\n\n    b = torch.randn(shape1, device=device, dtype=dtype)\n    b2 = b.clone()\n\n    b.requires_grad_(True)\n    b2.requires_grad_(True)\n\n    res_gt = a @ b\n    res = a_sparse @ b2\n\n    assert res.dtype == res_gt.dtype\n    assert torch.allclose(\n        res, res_gt, atol=atol\n    ), f\"{torch.max(torch.abs(res-res_gt))} - tolerance: {atol}\"\n\n    res_gt.sum().backward()\n    res.sum().backward()\n\n    a_grad = a.grad.clone().detach()\n    a_grad[~mask] = 0\n\n    assert torch.allclose(b.grad, b2.grad, atol=atol)\n    assert torch.allclose(\n        a_grad, a_sparse.grad.to_dense(), atol=atol\n    ), f\"{torch.max(torch.abs(a_grad-a_sparse.grad.to_dense()))}\"\n\n\n@disable_tf32\n@pytest.mark.parametrize(\"tensor_type\", _tensor_types)\n@pytest.mark.parametrize(\"device\", _devices)\ndef test_sparse_softmax(tensor_type, device):\n    N, C, H, W = 8, 2, 64, 64\n    dtype, atol = _get_dtype_atol(tensor_type, device)\n\n    sparsity = 0.8\n\n    shape0 = (N, C, H, W)\n    if tensor_type != BlockSparseTensor:\n        shape0 = shape0[1:]\n\n    a_sparse = _create_tensor(\n        tensor_type, device, dtype=dtype, shape=shape0, sparsity=sparsity\n    )\n    a = a_sparse.to_dense()\n    mask = a != 0\n\n    a[~mask] = float(\"-inf\")\n\n    a_sparse.requires_grad_(True)\n    a.requires_grad_(True)\n\n    res_gt = torch.softmax(a, dim=-1)\n    res_sparse = torch.softmax(a_sparse, dim=-1)\n\n    res = res_sparse.to_dense()\n\n    assert res.dtype == res_gt.dtype\n    assert torch.allclose(\n        res, res_gt, atol=atol\n    ), f\"{torch.max(torch.abs(res- res_gt))}\"\n\n    # WARNING: gradients are modified in-place!\n    res_sparse.values().backward(torch.ones_like(res_sparse.values()))\n    res_gt.backward(torch.ones_like(res_gt))\n\n    a_grad = a.grad.clone()\n    a_grad[~mask] = 0\n\n    assert torch.allclose(\n        a_grad, a_sparse.grad.to_dense(), atol=atol\n    ), f\"{torch.max(torch.abs(a_grad- a_sparse.grad.to_dense()))}\"\n\n\n@pytest.mark.parametrize(\"tensor_type\", _tensor_types)\n@pytest.mark.parametrize(\"device\", _devices)\ndef test_deepcopy(tensor_type, device):\n    import copy\n\n    N, C, H, W = 8, 2, 64, 64\n    dtype = torch.float32\n    sparsity = 0.8\n\n    shape0 = (N, C, H, W)\n    if tensor_type != BlockSparseTensor:\n        shape0 = shape0[1:]\n\n    a_sparse = _create_tensor(\n        tensor_type, device, dtype=dtype, shape=shape0, sparsity=sparsity\n    )\n\n    b_sparse = copy.deepcopy(a_sparse)\n    assert torch.equal(a_sparse, b_sparse)\n\n\n@pytest.mark.parametrize(\"tensor_type\", _tensor_types)\n@pytest.mark.parametrize(\"device\", _devices)\ndef test_module_buffer(tensor_type, device):\n    N, C, H, W = 8, 2, 64, 64\n    dtype = torch.float32\n    sparsity = 0.8\n\n    shape0 = (N, C, H, W)\n    if tensor_type != BlockSparseTensor:\n        shape0 = shape0[1:]\n\n    a_sparse = _create_tensor(\n        tensor_type, device, dtype=dtype, shape=shape0, sparsity=sparsity\n    )\n    b_sparse = _create_tensor(\n        tensor_type, device, dtype=dtype, shape=shape0, sparsity=sparsity\n    )\n\n    module = torch.nn.Module()\n    # test that register_buffer works\n    module.register_buffer(\"a_sparse\", a_sparse)\n\n    assert module.a_sparse is a_sparse\n\n    module.to(device)\n    assert module.a_sparse.device == torch.device(device)\n\n    state_dict = module.state_dict()\n    assert \"a_sparse\" in state_dict\n    assert torch.equal(a_sparse.to(device), state_dict[\"a_sparse\"])\n\n    module.load_state_dict(state_dict)\n\n    module.load_state_dict({\"a_sparse\": b_sparse})\n    assert torch.equal(module.a_sparse, b_sparse.to(device))\n"
  },
  {
    "path": "tests/test_sparsity24.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport functools\nimport random\nfrom typing import cast, Tuple\n\nimport pytest\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\n\nimport xformers  # noqa: F401\nimport xformers.ops as xops\nimport xformers.ops.sp24 as sp24\nfrom torch.sparse import to_sparse_semi_structured\n\nfrom .utils import assert_allclose\n\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\n\nrequires_sp24 = pytest.mark.skipif(compute_capability < (8, 0), reason=\"requires sm80+\")\nrequires_sp24_gemm = pytest.mark.skipif(\n    compute_capability != (8, 0), reason=\"requires sm80\"\n)\nrequires_cusparselt = pytest.mark.skipif(\n    not sp24._has_cusparseLt(), reason=\"requires cusparselt\"\n)\nrequires_h100_s24 = pytest.mark.skipif(\n    compute_capability != (9, 0)\n    or torch.version.cuda is None\n    or int(torch.version.cuda.split(\".\")[0]) < 12,\n    reason=\"requires sm90\",\n)\nparametrize_dtype = pytest.mark.parametrize(\n    \"dtype\", [torch.float16, torch.bfloat16], ids=[\"f16\", \"bf16\"]\n)\nparametrize_backend = pytest.mark.parametrize(\n    \"backend\",\n    (\n        [sp24.BACKEND_CUTLASS, sp24.BACKEND_CUSPARSELT]\n        if sp24._has_cusparseLt()\n        else [sp24.BACKEND_CUTLASS]\n    ),\n)\n\natol_rtol_kw = {\n    torch.float16: {\n        \"rtol\": 2e-3,\n        \"atol\": 1e-2,\n    },\n    torch.bfloat16: {\n        \"rtol\": 1e-1,\n        \"atol\": 1e-1,\n    },\n}\n\n\n@cuda_only\ndef test_sparse24_largest_mask_2d() -> None:\n    inp = torch.tensor(\n        [[4, 3, 2, 1], [0, 0, 0.5, 0.5], [1, 2, 3, 4], [10, 2, -1, 5]],\n        device=\"cuda\",\n        dtype=torch.float16,\n    )\n    out = torch.ops.xformers.sparse24_largest_mask_2d(inp)\n    assert out.int().tolist() == [\n        [1, 1, 0, 0],\n        [0, 1, 1, 0],\n        [0, 0, 1, 1],\n        [1, 0, 0, 1],\n    ]\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_sparse24_causal1122(dtype) -> None:\n    inp = torch.tensor(\n        [[4, 3, 2, 1], [-1, -3, 0.6, 0.5], [1, 2, 3, 4], [10, 2, -1, 5]],\n        device=\"cuda\",\n        dtype=dtype,\n    )\n    inp = F.pad(inp, (0, 128 - 4, 0, 128 - 4), \"constant\", 1)\n    sInp = sp24.sparsify24(inp, algo=\"causal1122\")\n\n    mask = sInp._sp24_to_dense() / inp\n    assert mask[:4, :4].int().tolist() == [\n        [1, 0, 0, 0],\n        [0, 0, 1, 0],\n        [0, 0, 1, 1],\n        [1, 0, 0, 1],\n    ]\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@parametrize_backend\ndef test_sparse24_largest_abs_values_greedy(dtype, backend) -> None:\n    inp = torch.tensor(\n        [[4, 3, 2, 1], [-1, -3, 0.6, 0.5], [1, 2, 3, 4], [10, 2, -1, 5]],\n        device=\"cuda\",\n        dtype=dtype,\n    )\n    inp = F.pad(inp, (0, 128 - 4, 0, 128 - 4), \"constant\", 1)\n    sInp = sp24.sparsify24(inp, algo=\"largest_abs_values_greedy\", backend=backend)\n\n    mask = sInp._sp24_to_dense() / inp\n    assert mask[:4, :4].int().tolist() == [\n        [1, 1, 0, 0],\n        [0, 1, 1, 0],\n        [0, 0, 1, 1],\n        [1, 0, 0, 1],\n    ]\n\n\n@cuda_only\n@parametrize_dtype\ndef test_sparse24_largest_mask_2d_notaligned(dtype) -> None:\n    inp = torch.randn([5, 5], device=\"cuda\", dtype=dtype)\n    with pytest.raises(RuntimeError):\n        torch.ops.xformers.sparse24_largest_mask_2d(inp)\n\n\n@cuda_only\n@parametrize_dtype\ndef test_sparse24_largest_mask_2d_big(dtype) -> None:\n    inp = torch.randn([2048, 2048], device=\"cuda\", dtype=dtype)\n    torch.ops.xformers.sparse24_largest_mask_2d(inp)\n\n\ndef create_random_mask(shape) -> torch.Tensor:\n    r = random.Random(0)\n    mask = torch.zeros(shape, dtype=torch.bool)\n    for line in range(mask.shape[0]):\n        for col in range(0, mask.shape[1], 4):\n            sparsity = r.choice(\n                [\n                    [False, False, True, True],\n                    [False, True, False, True],\n                    [True, False, False, True],\n                    [False, True, True, False],\n                    [True, False, True, False],\n                    [True, True, False, False],\n                ]\n            )\n            mask[line, col : col + 4] = torch.tensor(sparsity, dtype=torch.bool)\n    return mask\n\n\n@cuda_only\ndef test_detach_requires_grad() -> None:\n    x = torch.randn([128, 64], device=\"cuda\", dtype=torch.float16, requires_grad=True)\n    xs = sp24.sparsify24(x)\n    assert xs.requires_grad\n\n    # `detach` behavior\n    xs2 = xs.detach()\n    assert not xs2.requires_grad\n    assert not (xs2 * 2).requires_grad\n\n    xs2.requires_grad_(True)\n    assert xs2.requires_grad\n    ys = xs2 * 2\n    assert ys.requires_grad\n    ys.backward(ys)\n\n\n@cuda_only\ndef test_detach2() -> None:\n    x = torch.randn([128, 64], device=\"cuda\", dtype=torch.float16, requires_grad=False)\n    assert not sp24.sparsify24(x).requires_grad\n    x.requires_grad_(True)\n    xs = sp24.sparsify24(x)\n    assert xs.requires_grad\n    xs2 = xs.detach()\n    xs2.requires_grad_(True)\n    xs3 = xs2 * 2\n    assert xs3.requires_grad\n    xs3.backward(xs3)\n    assert xs2.grad is not None\n    assert x.grad is None\n\n\n@cuda_only\ndef test_meta_pack_and_reorder() -> None:\n    mask = create_random_mask([32, 64])\n    # Test a specific line with a known pattern\n    line = 3\n    mask[line, :16] = torch.tensor(\n        [\n            False,\n            True,\n            True,\n            False,  # 1 << 0 | 2 << 2\n            True,\n            True,\n            False,\n            False,  # 0 << 4 | 1 << 6\n            True,\n            False,\n            False,\n            True,  # 0 << 8 | 3 << 10\n            False,\n            True,\n            True,\n            False,  # 1 << 12 | 2 << 14\n        ],\n        dtype=torch.bool,\n    )\n    packed = torch.ops.xformers._sparse24_pack_mask(mask)\n    assert packed.shape == (mask.shape[0], mask.shape[1] // 16)\n    # cast int16 -> uint16\n    value_packed = (packed[line, 0].item() + (1 << 16)) % (1 << 16)\n    expected_value = (\n        1 << 0 | 2 << 2 | 0 << 4 | 1 << 6 | 0 << 8 | 3 << 10 | 1 << 12 | 2 << 14\n    )\n    assert value_packed == expected_value\n\n    meta_reordered = torch.ops.xformers._sparse24_reorder_meta(packed)\n    assert meta_reordered.ndim == 3\n    assert meta_reordered.shape[0] == packed.shape[0]\n\n    assert (meta_reordered[0, 0, 0] == packed[0, 0]).item()\n    assert (meta_reordered[0, 1, 0] == packed[8, 0]).item()\n    assert (meta_reordered[1, 0, 0] == packed[0, 1]).item()\n    assert (meta_reordered[1, 1, 0] == packed[8, 1]).item()\n    assert (meta_reordered[2, 0, 0] == packed[16, 0]).item()\n    assert (meta_reordered[2, 1, 0] == packed[24, 0]).item()\n    # second column\n    assert (meta_reordered[0, 0, 1] == packed[0, 2]).item()\n\n\n@cuda_only\ndef test_pack_tensor_according_to_mask() -> None:\n    mask = create_random_mask([32, 64])\n    # Test a specific line with a known pattern\n    line = 3\n    line_pattern = [\n        False,\n        True,\n        True,\n        False,\n        True,\n        True,\n        False,\n        False,\n        True,\n        False,\n        False,\n        True,\n        False,\n        True,\n        True,\n        False,\n    ]\n    mask[line, :16] = torch.tensor(line_pattern, dtype=torch.bool)\n    packed = torch.ops.xformers._sparse24_pack_mask(mask)\n    reordered = torch.ops.xformers._sparse24_reorder_meta(packed)\n\n    a_full = torch.randn(mask.shape, dtype=torch.float16)\n    a_packed = torch.ops.xformers._sparse24_pack_tensor_according_to_mask(\n        a_full, reordered\n    )\n    line_full = a_full[line, :16].tolist()\n    line_packed = a_packed[line, :8].tolist()\n    line_packed_expected = [\n        value for index, value in enumerate(line_full) if line_pattern[index]\n    ]\n    assert line_packed == line_packed_expected\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_sp24_gemm(dtype) -> None:\n    M, N, K = 32, 32, 64\n    a = torch.randn([M, K], device=\"cuda\", dtype=dtype)\n    b = torch.randn([K, N], device=\"cuda\", dtype=dtype)\n    mask = create_random_mask([M, K])\n    mask_packed = torch.ops.xformers._sparse24_pack_mask(mask)\n    mask_reordered = torch.ops.xformers._sparse24_reorder_meta(mask_packed)\n    packed_a = torch.ops.xformers._sparse24_pack_tensor_according_to_mask(\n        a.cpu(), mask_reordered\n    )\n    packed_a = packed_a.cuda()\n\n    mask_reordered = mask_reordered.cuda()\n    mask = mask.to(dtype).cuda()\n    masked_a = a * mask\n\n    ref_out = masked_a @ b\n    sp24_out = torch.ops.xformers._sparse24_gemm(packed_a, b, mask_reordered)\n    assert_allclose(ref_out, sp24_out, msg=\"sp24 GEMM\", **atol_rtol_kw[dtype])\n\n\n@cuda_only\n@pytest.mark.parametrize(\"transpose\", [True, False])\ndef test_pack_meta_shuffle(transpose: bool) -> None:\n    local_meta = torch.zeros([4, 8, 8], dtype=torch.int64, device=\"cuda\")\n    local_meta[:2, :2] = torch.randint(\n        0, 256, size=(2, 2, 8), dtype=torch.int64, device=\"cuda\"\n    )\n    final_meta_tensor = torch.ops.xformers._sparse24_meta_shuffle_test(\n        local_meta, transpose\n    )\n    assert final_meta_tensor[2:, 2:].abs().max().item() == 0\n    final_meta = final_meta_tensor.tolist()\n\n    def pack(line):\n        if transpose:\n            return int(\n                local_meta[0, 0, line]\n                | (local_meta[1, 0, line] << 8)\n                | (local_meta[0, 1, line] << 16)\n                | (local_meta[1, 1, line] << 24)\n            )\n        else:\n            return int(\n                local_meta[0, 0, line]\n                | (local_meta[0, 1, line] << 8)\n                | (local_meta[1, 0, line] << 16)\n                | (local_meta[1, 1, line] << 24)\n            )\n\n    def meta_str(m):\n        return \" \".join(f\"0x{mm:02x}\" for mm in m.tolist())\n\n    def expect_match(i, j, line):\n        value = final_meta[i][j][0]\n        expected = pack(line)\n        assert value == expected, f\"\"\"value: 0x{value:02x} (expected: 0x{expected:02x})\n{meta_str(local_meta[0, 0, :4])} (T0) |||| {meta_str(local_meta[0, 1, :4])} (T4)\n{meta_str(local_meta[1, 0, :4])} (T1) |||| {meta_str(local_meta[1, 1, :4])} (T5)\n\"\"\"\n\n    expect_match(0, 0, 0)  # T0\n    if transpose:\n        expect_match(1, 0, 1)  # T1\n        expect_match(0, 1, 2)  # T4\n    else:\n        expect_match(0, 1, 1)  # T4\n        expect_match(1, 0, 2)  # T1\n    expect_match(1, 1, 3)  # T5\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@parametrize_backend\ndef test_pack_both_ways_meta_correctness(dtype, backend) -> None:\n    M, N = 256, 512\n    # Construct x to make sure we always have exactly 8 elements per 4x4 tile\n    a = _gen_24_sparsifiable_both_ways(M, N, dtype)\n    assert a.shape == (M, N)\n    a = a.cuda().to(dtype)\n    b = torch.randn([a.shape[1], 128], device=\"cuda\", dtype=dtype)\n    a_sparse = sp24.sparsify24(a, backend=backend)\n\n    mask_dense = torch.ops.xformers.sparse24_largest_mask_2d(a)\n\n    if backend == sp24.BACKEND_CUTLASS:\n        assert isinstance(a_sparse, sp24.Sparse24TensorCutlass)\n        mask_packed = torch.ops.xformers._sparse24_pack_mask(mask_dense.cpu().bool())\n        mask_reordered = torch.ops.xformers._sparse24_reorder_meta(mask_packed).cuda()\n        assert torch.allclose(a_sparse.meta.view(torch.short), mask_reordered)\n    ref_gemm = (mask_dense * a) @ b\n    pack_gemm = a_sparse @ b\n    renorm = ref_gemm.std().item()\n    assert_allclose(\n        ref_gemm.float() / renorm,\n        pack_gemm.float() / renorm,\n        msg=\"sp24 GEMM\",\n        **atol_rtol_kw[dtype],\n    )\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_pack_both_ways_id(dtype) -> None:\n    N = 512\n    torch.manual_seed(0)\n    a = torch.randn([N, N], dtype=dtype, device=\"cuda\")\n    b = torch.eye(N, dtype=dtype, device=\"cuda\")\n\n    packed, meta, packed_t, meta_t = torch.ops.xformers.sparse24_sparsify_both_ways(a)[\n        :4\n    ]\n    # Heuristic to ensure we pack the same values\n    assert torch.allclose(\n        packed.to(torch.float64).sum(), packed_t.to(torch.float64).sum()\n    )\n\n    mask_dense = torch.ops.xformers.sparse24_largest_mask_2d(a.to(dtype))\n\n    ref_gemm = mask_dense * a\n    # Test A@B\n    pack_gemm = torch.ops.xformers._sparse24_gemm(packed, b, meta)\n    max_diff = (ref_gemm - pack_gemm).abs().argmax()\n    assert torch.allclose(\n        ref_gemm, pack_gemm\n    ), f\"packed is wrong at pos: ({max_diff // N}, {max_diff % N})\"\n    # Test A.t@B\n    pack_gemm = torch.ops.xformers._sparse24_gemm(packed_t, b, meta_t)\n    pack_gemm = pack_gemm.transpose(0, 1)\n    max_diff = (ref_gemm - pack_gemm).abs().argmax()\n    assert torch.allclose(\n        ref_gemm, pack_gemm\n    ), f\"packed_t is wrong at pos: ({max_diff // N}, {max_diff % N})\"\n\n\n@cuda_only\n@parametrize_dtype\ndef test_pack_both_ways_edge_case1(dtype) -> None:\n    # In this case, the heuristic will keep 7 values out of 16\n    # instead of 8. let's see how the kernel handles this\n    quad = torch.tensor(\n        [\n            [2, -1, -2, -3],  # Should be packed as `2 <null>`\n            [-1, 8, -1, 6],\n            [-1, -1, 4, 5],\n            [-1, 3, 7, -1],\n        ],\n        dtype=dtype,\n        device=\"cuda\",\n    )\n    a = torch.randn([32, 64], dtype=dtype, device=\"cuda\")\n    a[:4, :4] = quad\n    packed, meta, packed_t, meta_t = torch.ops.xformers.sparse24_sparsify_both_ways(a)[\n        :4\n    ]\n    # Check first line in A\n    assert packed[0, 0].item() == 2\n    assert packed[0, 1].item() == 0\n    # And first column in A.t\n    assert packed_t[0, 0].item() == 2\n    assert packed_t[0, 1].item() == 0\n\n\n@cuda_only\n@parametrize_dtype\ndef test_sp24_apply(dtype) -> None:\n    M, N = 256, 1024\n    x = torch.randn([M, N], dtype=dtype, device=\"cuda\")\n    (\n        packed,\n        meta,\n        packed_t,\n        meta_t,\n        threads_masks,\n    ) = torch.ops.xformers.sparse24_sparsify_both_ways(x)\n    packed2, _, packed_t2, _ = torch.ops.xformers.sparse24_apply(x, threads_masks)\n    assert torch.allclose(packed, packed2)\n    assert torch.allclose(packed_t, packed_t2)\n\n\n@cuda_only\n@parametrize_dtype\ndef test_sp24_api_different_pattern(dtype) -> None:\n    M, N = 256, 256\n    x = torch.randn([M, N], dtype=dtype, device=\"cuda\")\n    y = torch.randn([M, N], dtype=dtype, device=\"cuda\")\n    sx = sp24.sparsify24(x)\n    sy = sp24.sparsify24(y)\n    # Can't add with different sparsity pattern\n    with pytest.raises(ValueError):\n        sx + sy\n    # Ok, same sparsity pattern\n    assert isinstance(sx + sx, sp24.Sparse24Tensor)\n    # Ok, sharing sparsity pattern of x\n    sy2 = sp24.sparsify24_like(y, sx)\n    assert isinstance(sx + sy2, sp24.Sparse24Tensor)\n\n\n@cuda_only\n@parametrize_dtype\ndef test_sp24_api_different_pattern_transposed(dtype) -> None:\n    N = 256\n    x = torch.randn([N, N], dtype=dtype, device=\"cuda\")\n    sx = sp24.sparsify24(x, backend=sp24.BACKEND_CUTLASS)\n    sxt = sx.t()\n    assert isinstance(sxt, sp24.Sparse24Tensor)\n    # Can't add with different sparsity pattern\n    with pytest.raises(ValueError):\n        sx + sxt\n    # But this should work\n    sx + sxt.t()\n    # And we should be able to sparsify with transposed pattern\n    sxt2 = sp24.sparsify24_like(x.t(), sxt)\n    assert torch.allclose(sxt2.packed, sxt.packed)\n    assert torch.allclose(sxt2.packed_t, sxt.packed_t)\n\n\ndef _gen4x4(r: random.Random):\n    # Create a 4x4 tile that can be 24 sparsified perfectly\n    values = [\n        [1, 1, 0, 0],\n        [0, 1, 1, 0],\n        [0, 0, 1, 1],\n        [1, 0, 0, 1],\n    ]\n    c1, c2 = r.sample([0, 1, 2, 3], 2)\n    r1, r2 = r.sample([0, 1, 2, 3], 2)\n    values[r1], values[r2] = values[r2], values[r1]\n    for i in range(4):\n        values[i][c1], values[i][c2] = values[i][c2], values[i][c1]\n    return values\n\n\ndef _gen_24_sparsifiable_both_ways(\n    M: int, N: int, dtype, seed: int = 0\n) -> torch.Tensor:\n    torch.manual_seed(0)\n    r = random.Random(0)\n\n    a = torch.zeros([M, N], device=\"cuda\", dtype=torch.float16)\n    assert M % 4 == 0 and N % 4 == 0\n    for m in range(0, M, 4):\n        for n in range(0, N, 4):\n            a[m : m + 4, n : n + 4] = torch.tensor(\n                _gen4x4(r), device=\"cuda\", dtype=torch.float16\n            )\n    a = a * torch.randn_like(a).abs()\n    return a\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@parametrize_backend\ndef test_sp24_transpose_invariant(dtype, backend) -> None:\n    M, N = 128, 256\n\n    a = _gen_24_sparsifiable_both_ways(M, N, dtype)\n\n    # Sparsify `a`` and `a.t()`\n    a_s = sp24.sparsify24(a, backend=backend)\n    a_t_s = sp24.sparsify24(a.t().contiguous(), backend=backend)\n\n    assert_allclose(a_s.packed, a_t_s.packed_t)\n    assert_allclose(a_s.meta, a_t_s.meta_t)\n    assert_allclose(a_t_s.packed, a_s.packed_t)\n    assert_allclose(a_t_s.meta, a_s.meta_t)\n\n    assert_allclose(a_s._sp24_to_dense(), a_t_s.t()._sp24_to_dense())  # type: ignore\n    assert_allclose(a_s.t()._sp24_to_dense(), a_t_s._sp24_to_dense())  # type: ignore\n\n    assert_allclose(a_s._sp24_to_dense(), a)\n    assert_allclose(a_t_s.t()._sp24_to_dense(), a)  # type: ignore\n    assert_allclose(a_t_s._sp24_to_dense().t(), a)\n\n\n@requires_cusparselt\n@requires_sp24_gemm\n@pytest.mark.parametrize(\"M\", [128, 256, 512])\n@pytest.mark.parametrize(\"N\", [128, 256])\ndef test_cusparselt_format(M: int, N: int) -> None:\n    a = _gen_24_sparsifiable_both_ways(M, N, torch.float16)\n    a_s = sp24.sparsify24(a, backend=\"cusparselt\")\n    ref_a_s = to_sparse_semi_structured(a)\n    ref_a_t_s = to_sparse_semi_structured(a.t().contiguous())\n\n    assert_allclose(a_s.packed, ref_a_s.packed)\n    assert_allclose(a_s.packed_t, ref_a_t_s.packed)\n\n    assert_allclose(a_s._sp24_to_dense(), a)\n    assert_allclose(a_s.t()._sp24_to_dense(), a.t())  # type: ignore[attr-defined]\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_sp24_matmuls(dtype) -> None:\n    M, N, K = 64, 256, 1024\n    a = torch.randn([M, K], device=\"cuda\", dtype=dtype)\n    b = torch.randn([K, N], device=\"cuda\", dtype=dtype)\n    a_m = torch.ops.xformers.sparse24_largest_mask_2d(a)\n    b_m = torch.ops.xformers.sparse24_largest_mask_2d(b)\n    a_s = sp24.sparsify24(a)\n    b_s = sp24.sparsify24(b)\n\n    assert_allclose(a_s @ b, (a * a_m) @ b, msg=\"sp@dense\", **atol_rtol_kw[dtype])\n    assert_allclose(a @ b_s, a @ (b * b_m), msg=\"dense@sp\", **atol_rtol_kw[dtype])\n    assert_allclose(\n        a @ a_s.t(), a @ (a * a_m).t(), msg=\"dense@sp.t\", **atol_rtol_kw[dtype]\n    )\n    assert_allclose(\n        a_s.t() @ a, (a * a_m).t() @ a, msg=\"sp.t@dense\", **atol_rtol_kw[dtype]\n    )\n\n\n@requires_sp24\ndef test_sp24_matmuls_mat_vec() -> None:\n    a = torch.randn([64, 128], device=\"cuda\", dtype=torch.float16)\n    b = torch.randn([128], device=\"cuda\", dtype=torch.float16)\n    a_m = torch.ops.xformers.sparse24_largest_mask_2d(a)\n    a_s = sp24.sparsify24(a)\n\n    with pytest.raises(NotImplementedError):\n        assert_allclose(a_s @ b, (a * a_m) @ b, msg=\"sp@dense\", **atol_rtol_kw[a.dtype])\n\n\n@requires_sp24\ndef test_sp24_matmuls_bmm() -> None:\n    a = torch.randn([64, 128], device=\"cuda\", dtype=torch.float16)\n    b = torch.randn([5, 6, 128], device=\"cuda\", dtype=torch.float16)\n    a_m = torch.ops.xformers.sparse24_largest_mask_2d(a)\n    a_s = sp24.sparsify24(a)\n\n    with pytest.raises(NotImplementedError):\n        assert_allclose(a_s @ b, (a * a_m) @ b, msg=\"sp@dense\", **atol_rtol_kw[a.dtype])\n\n\ndef sparsify24_dense(tensor: torch.Tensor):\n    m = torch.ops.xformers.sparse24_largest_mask_2d(tensor)\n    return m * tensor\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@pytest.mark.parametrize(\"act\", [F.gelu, F.relu])\ndef test_sp24_api_mlp_act24_correctness(dtype, act) -> None:\n    B, in_ft, hid_ft, out_ft = 256, 2048, 6144, 2048\n    torch.manual_seed(0)\n    x = torch.randn([B, in_ft], dtype=dtype, device=\"cuda\", requires_grad=True)\n    w1 = (\n        torch.randn([in_ft, hid_ft], dtype=dtype, device=\"cuda\", requires_grad=False)\n        * 0.01\n    )\n    w2 = (\n        torch.randn([hid_ft, out_ft], dtype=dtype, device=\"cuda\", requires_grad=False)\n        * 0.01\n    )\n    grad = (\n        torch.randn([B, out_ft], dtype=dtype, device=\"cuda\", requires_grad=False) * 0.1\n    )\n    w1.requires_grad_(True)\n    w2.requires_grad_(True)\n\n    params_with_grads = [x, w1, w2]\n\n    # Run baseline\n    x1 = x @ w1\n    x1 = sparsify24_dense(x1)\n    x1 = act(x1)\n    out = x1 @ w2\n    out.backward(grad)\n\n    grads_ref = [t.grad for t in params_with_grads]\n    for t in params_with_grads:\n        t.grad = None\n\n    # Run with sparsity\n    x1 = x @ w1\n    x1 = sp24.sparsify24(x1)\n    x1 = act(x1)\n    out = x1 @ w2\n    out.backward(grad)\n\n    for grad_name, grad_ref, grad_calc in zip(\n        [\"x\", \"w1\", \"w2\"], grads_ref, [t.grad for t in params_with_grads]\n    ):\n        assert grad_calc is not None, grad_name\n        assert grad_ref is not None, grad_name\n        assert_allclose(grad_calc, grad_ref, msg=grad_name, **atol_rtol_kw[dtype])\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_sp24_api_swiglu_correctness(dtype) -> None:\n    B, in_ft, hid_ft, out_ft = 256, 2048, 6144 // 2, 2048\n    torch.manual_seed(0)\n    x = torch.randn([B, in_ft], dtype=dtype, device=\"cuda\", requires_grad=True)\n    w1 = (\n        torch.randn([in_ft, hid_ft], dtype=dtype, device=\"cuda\", requires_grad=False)\n        * 0.01\n    )\n    w2 = (\n        torch.randn([in_ft, hid_ft], dtype=dtype, device=\"cuda\", requires_grad=False)\n        * 0.01\n    )\n    w3 = (\n        torch.randn([hid_ft, out_ft], dtype=dtype, device=\"cuda\", requires_grad=False)\n        * 0.01\n    )\n    grad = (\n        torch.randn([B, out_ft], dtype=dtype, device=\"cuda\", requires_grad=False) * 0.1\n    )\n    w1.requires_grad_(True)\n    w2.requires_grad_(True)\n    w3.requires_grad_(True)\n\n    params_with_grads = [x, w1, w2, w3]\n\n    # Run baseline\n    x1 = x @ w1\n    x2 = x @ w2\n    x1s = sparsify24_dense(F.silu(x1))\n    hid = x1s * x2\n    out = hid @ w3\n    out.backward(grad)\n\n    grads_ref = [t.grad for t in params_with_grads]\n    for t in params_with_grads:\n        t.grad = None\n\n    # Run with sparsity\n    x1 = x @ w1\n    x2 = x @ w2\n    x1s = sp24.sparsify24(F.silu(x1))\n    hid = x1s * x2\n    out = hid @ w3\n    out.backward(grad)\n\n    for grad_name, grad_ref, grad_calc in zip(\n        [\"x\", \"w1\", \"w2\", \"w3\"], grads_ref, [t.grad for t in params_with_grads]\n    ):\n        assert grad_calc is not None, grad_name\n        assert grad_ref is not None, grad_name\n        assert_allclose(grad_calc, grad_ref, msg=grad_name, **atol_rtol_kw[dtype])\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@pytest.mark.parametrize(\"M\", [1, 8, 26, 31, 32, 48, 63])\ndef test_not_aligned(dtype, M):\n    N, K = 64, 128\n    A = torch.randn([M, K], dtype=dtype, device=\"cuda\")\n    B = torch.randn([K, N], dtype=dtype, device=\"cuda\")\n    As = sp24.sparsify24(A)\n    A = As._sp24_to_dense()\n    assert tuple(A.shape) == (M, K), A.shape\n    assert_allclose(As @ B, A @ B, msg=\"not aligned\", **atol_rtol_kw[dtype])\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@parametrize_backend\n@pytest.mark.parametrize(\"input_rowmajor\", [True, False])\ndef test_sparsify24_like_dense(dtype, input_rowmajor, backend):\n    M, N = 128, 256\n    if input_rowmajor:\n        x = torch.randn([M, N], dtype=dtype, device=\"cuda\")\n    else:\n        x = torch.randn([N, M], dtype=dtype, device=\"cuda\").t()\n    sx = sp24.sparsify24(x.contiguous(), backend=backend)\n    sx_like = sp24.sparsify24_like(x, pattern=sx, backend=\"dense\")\n    assert_allclose(\n        sx_like, sx._sp24_to_dense(), msg=\"sp24_like\", **atol_rtol_kw[dtype]\n    )\n\n\n@requires_sp24_gemm\n@parametrize_dtype\n@parametrize_backend\ndef test_sparsify24_weights(dtype, backend):\n    x = torch.randn([128, 512], dtype=dtype, device=\"cuda\", requires_grad=True)\n    w = torch.randn([1024, 512], dtype=dtype, device=\"cuda\", requires_grad=True)\n\n    flat_w = w.flatten()  # FSDP-like processing\n    w = flat_w.reshape(w.shape)\n\n    sw = sp24.sparsify24(w, gradient=\"24dense\", backend=backend)\n    y = x @ sw.t()\n\n    y.backward(y)\n\n\nclass LinearW24(torch.nn.Linear):\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        input_shape = input.shape\n        input = input.flatten(end_dim=-2)\n        dim0 = input.shape[0]\n        if dim0 % 8 != 0:\n            # NOTE: This should be torch-compiled away\n            input = F.pad(input, [0, 0, 0, -dim0 % 8])\n        w_sparse = xops.sparsify24(\n            self.weight,\n            gradient=\"24dense\",\n            backend=\"cusparselt\",\n        )\n        return F.linear(\n            input,\n            w_sparse,\n            self.bias,\n        )[\n            :dim0\n        ].unflatten(dim=0, sizes=input_shape[:-1])\n\n\n# XXX: This is needed to avoid a CUDA internal error\n# See the issue here:\n# https://github.com/pytorch/pytorch/issues/113776\n@functools.lru_cache()\ndef _workaround_cusparselt_internal_error() -> None:\n    x0 = torch.randn([128, 128], device=\"cuda\", dtype=torch.float16, requires_grad=True)\n    m = LinearW24(128, 128, bias=False).cuda().to(torch.float16)\n    out = m(x0)\n    out.backward(out)\n\n\n@requires_sp24\n@parametrize_dtype\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\n@pytest.mark.parametrize(\"bias\", [False, True], ids=[\"\", \"bias\"])\n@pytest.mark.parametrize(\"aligned\", [False, True], ids=[\"misaligned\", \"\"])\n@pytest.mark.parametrize(\"amp\", [False], ids=[\"\"])\ndef test_linearw24(dtype, bias: bool, aligned: bool, amp: bool) -> None:\n    _workaround_cusparselt_internal_error()\n\n    B, ft_in, ft_out = 64, 128, 256\n    if not aligned:\n        B = 65\n    model_dtype = torch.float32 if amp else dtype\n    x = torch.randn([B, ft_in], device=\"cuda\", dtype=model_dtype, requires_grad=True)\n    grad = torch.randn([B, ft_out], device=\"cuda\", dtype=model_dtype)\n    m = torch.nn.Linear(ft_in, ft_out, bias=bias).cuda().to(model_dtype)\n    m24 = LinearW24(ft_in, ft_out, bias=bias).cuda().to(model_dtype)\n\n    with torch.autocast(\"cuda\", dtype=dtype, enabled=amp):\n        # Make weights sparse\n        state_dict = m.state_dict()\n        weight_sp24 = sp24.sparsify24(state_dict[\"weight\"].abs())\n        state_dict[\"weight\"] = weight_sp24._sp24_to_dense().to(model_dtype).detach()\n        m.load_state_dict(state_dict)\n        m24.load_state_dict(state_dict)\n\n        # FW with dense weights\n        out = m(x)\n\n        # FW with sparsity\n        x24 = x.detach().requires_grad_()\n        out24 = m24(x24)\n\n    # Backward passes outside autocast\n    out.backward(grad)\n    out24.backward(grad)\n\n    assert out24.is_contiguous()\n    assert x24.grad is not None\n    assert x24.grad.is_contiguous()\n    assert m24.weight.grad is not None\n    assert m24.weight.grad.is_contiguous()\n    if bias:\n        assert m24.bias.grad is not None\n\n    assert_allclose(out24, out, msg=\"output\", **atol_rtol_kw[dtype])\n    assert x.grad is not None and x24.grad is not None\n    assert_allclose(x24.grad, x.grad, msg=\"x.grad\", **atol_rtol_kw[dtype])\n    assert m.weight.grad is not None\n    assert_allclose(\n        m24.weight.grad.to(dtype),\n        sp24.sparsify24_like(\n            m.weight.grad.to(dtype), pattern=weight_sp24, out_dense=True\n        ),\n        msg=\"w.grad\",\n        **atol_rtol_kw[dtype],\n    )\n    if bias:\n        assert m.bias.grad is not None\n        assert m24.bias.grad is not None\n        assert_allclose(\n            m24.bias.grad.to(dtype),\n            m.bias.grad.to(dtype),\n            msg=\"bias.grad\",\n            **atol_rtol_kw[dtype],\n        )\n\n\n@requires_sp24\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_wrong_alignment_error_message() -> None:\n    A = torch.randn([128, 128], device=\"cuda\", dtype=torch.float16)\n    B = torch.randn([128, 4], device=\"cuda\", dtype=torch.float16)\n    A = sp24.sparsify24(A, backend=\"cusparselt\")\n    with pytest.raises(NotImplementedError, match=\"aligned to 8\"):\n        A @ B\n\n\n@requires_sp24\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_min_alignment() -> None:\n    A = torch.randn([128, 128], device=\"cuda\", dtype=torch.float16)\n    B = torch.randn([128, 8], device=\"cuda\", dtype=torch.float16)\n    A = sp24.sparsify24(A, backend=\"cusparselt\")\n    assert_allclose(A @ B, A._sp24_to_dense() @ B, \"output\", **atol_rtol_kw[A.dtype])\n\n\n@requires_sp24\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_wrong_dtype_error_message() -> None:\n    A = torch.randn([128, 128], device=\"cuda\", dtype=torch.float16)\n    B = torch.randn([128, 16], device=\"cuda\", dtype=torch.float32)\n    A = sp24.sparsify24(A, backend=\"cusparselt\")\n    with pytest.raises(NotImplementedError, match=\"the same data type\"):\n        A @ B\n\n\n@requires_sp24_gemm\n@parametrize_backend\n@pytest.mark.parametrize(\"with_bias\", [False, True])\ndef test_linear_dispatch_inference_mode(backend: str, with_bias: bool) -> None:\n    B, ft_in, ft_out = 128, 256, 512\n    x = torch.randn([B, ft_in], device=\"cuda\", dtype=torch.float16)\n    weight = torch.randn([ft_out, ft_in], device=\"cuda\", dtype=torch.float16)\n    bias = (\n        torch.randn([ft_out], device=\"cuda\", dtype=torch.float16) if with_bias else None\n    )\n\n    w_sparse = sp24.sparsify24(\n        weight,\n        gradient=\"24dense\",\n        backend=backend,\n    )\n    # NOTE: When in `inference_mode`, PyTorch no longer dispatches to `addmm`, but to `linear`\n    # so we need to support that as well\n    with torch.inference_mode():\n        # Does not support bias at the moment in CUTLASS backend\n        if bias is not None and backend == sp24.BACKEND_CUTLASS:\n            with pytest.raises(NotImplementedError):\n                F.linear(x, w_sparse, bias)\n            return\n        out = F.linear(x, w_sparse, bias)\n    out_ref = F.linear(x, w_sparse._sp24_to_dense(), bias)\n    assert_allclose(out, out_ref, msg=\"output\", **atol_rtol_kw[x.dtype])\n\n\n@cuda_only\ndef test_sp24_meta() -> None:\n    x = torch.randn([1024, 512], device=\"meta\", dtype=torch.float16)\n    x_s = sp24.sparsify24(x, backend=\"cusparselt\")\n    assert x_s.shape == x.shape\n    x_s_t = x_s.t()\n    assert x_s_t.shape == x.t().shape\n\n\n@requires_sp24_gemm\n@parametrize_backend\ndef test_sp24_compile(backend) -> None:\n    x = torch.randn([1024, 512], device=\"cuda\", dtype=torch.float16, requires_grad=True)\n    e = torch.eye(x.shape[0], x.shape[0], device=\"cuda\", dtype=torch.float16)\n\n    def fn(x, e):\n        y = sp24.sparsify24(x, backend=backend, gradient=\"24dense\")\n        y = y.t()\n        return x @ y\n\n    # Eager\n    output = fn(x, e)\n    output.backward(output)\n    # Torch compile\n    output = torch.compile(fn)(x, e)\n    output.backward(output)\n\n\nclass _TransformerFFN(nn.Module):\n    def __init__(\n        self,\n        in_features: int,\n        hidden_features=None,\n        out_features=None,\n        act_layer=nn.GELU,\n        bias: bool = True,\n        linear_cls=nn.Linear,\n    ) -> None:\n        super().__init__()\n        out_features = out_features or in_features\n        hidden_features = hidden_features or in_features\n        self.fc1 = linear_cls(in_features, hidden_features, bias=bias)\n        self.act = act_layer()\n        self.fc2 = linear_cls(hidden_features, out_features, bias=bias)\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        x = self.fc1(x)\n        x = self.act(x)\n        x = self.fc2(x)\n        return x\n\n\n@requires_sp24_gemm\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_linearw24_block_compile() -> None:\n    # TODO: Parametrize on `dtype` when torch.compile gets faster\n    # currently takes ~5s per test\n    dtype = torch.bfloat16\n    B, FT_IN, FT_HIDDEN = 31, 512, 2048\n\n    _workaround_cusparselt_internal_error()\n    m = _TransformerFFN(FT_IN, FT_HIDDEN, linear_cls=LinearW24).to(\"cuda\").to(dtype)\n    m_c = _TransformerFFN(FT_IN, FT_HIDDEN, linear_cls=LinearW24).to(\"cuda\").to(dtype)\n    m_c.load_state_dict(m.state_dict())\n    m_c = cast(_TransformerFFN, torch.compile(m_c))\n\n    x, grad = [torch.randn([B, FT_IN], dtype=dtype, device=\"cuda\") for _ in range(2)]\n    x = x.requires_grad_()\n    out = m(x)\n    out.backward(grad)\n\n    x_c = x.detach().requires_grad_()\n    out_c = m_c(x_c)\n    out_c.backward(grad)\n\n    assert_allclose(out_c, out, \"output\", **atol_rtol_kw[dtype])\n    assert x_c.grad is not None and x.grad is not None\n    assert_allclose(x_c.grad, x.grad, \"output\", **atol_rtol_kw[dtype])\n    for param_name, param_ref, param_c in [\n        [\"fc1.weight\", m.fc1.weight, m_c.fc1.weight],\n        [\"fc1.bias\", m.fc1.bias, m_c.fc1.bias],\n        [\"fc2.weight\", m.fc2.weight, m_c.fc2.weight],\n        [\"fc2.bias\", m.fc2.bias, m_c.fc2.bias],\n    ]:\n        assert param_ref.grad is not None and param_c.grad is not None, param_name\n        assert_allclose(param_c.grad, param_ref.grad, param_name, **atol_rtol_kw[dtype])\n\n\n@requires_sp24\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_sp24_ste():\n    x = torch.randn([512, 512], dtype=torch.float16, device=\"cuda\", requires_grad=True)\n    grad = torch.randn_like(x)\n    spX = sp24.sparsify24(x, gradient=sp24.GRADIENT_STE)\n    spX.backward(grad)\n    assert_allclose(x.grad, grad, \"grad\")\n\n\n@requires_sp24_gemm\n@parametrize_dtype\ndef test_sparsify24_ste(dtype):\n    x = torch.randn([512, 512], dtype=dtype, device=\"cuda\", requires_grad=True)\n    y = torch.randn([512, 512], dtype=dtype, device=\"cuda\", requires_grad=True)\n    mul0 = 2.0  # (numbers that have an exact representation in f16)\n    mul1 = 0.5\n    spX = sp24.sparsify24_ste(x, bw_mul0=mul0, bw_mul1=mul1)\n    spX.backward(y)\n    spYd = sp24.sparsify24_like(y, pattern=spX)._sp24_to_dense()\n    ref = mul1 * (spYd) + mul0 * (y - spYd)\n    assert_allclose(x.grad, ref, \"grad\")\n\n\nclass _Sp24X(torch.autograd.Function):\n    @staticmethod\n    def forward(ctx, x):\n        xs = sp24.sparsify24(x, backend=\"cusparselt\", algo=\"largest_values_greedy\")\n        ctx.threads_masks = xs.threads_masks\n        ctx.meta = xs.meta.clone()\n        ctx.meta_t = xs.meta_t.clone()\n        return xs\n\n    @staticmethod\n    def backward(ctx, x):\n        packed, meta, packed_t, meta_t = sp24.SparsifyApply.OPERATOR(\n            x, ctx.threads_masks, backend=\"cusparselt\"\n        )\n        meta.copy_(ctx.meta)\n        meta_t.copy_(ctx.meta_t)\n        return sp24.Sparse24TensorCuSparseLt(\n            x.shape,\n            packed,\n            meta,\n            packed_t,\n            meta_t,\n            ctx.threads_masks,\n            requires_grad=False,\n        )\n\n\n@requires_sp24_gemm\n@pytest.mark.skipif(not sp24._has_cusparseLt(), reason=\"requires cusparselt\")\ndef test_compile_unflatten():\n    x = torch.randn(\n        [1024, 1024], device=\"cuda\", dtype=torch.float16, requires_grad=True\n    )\n    fnc = torch.compile(_Sp24X.apply)\n    fnc(x)\n\n\ndef _to_fp8_rowwise(x: torch.Tensor, dtype) -> Tuple[torch.Tensor, torch.Tensor]:\n    max_v = torch.finfo(dtype).max\n    x_scale = (x.abs().max(1, keepdim=True)[0] / max_v).float()\n    x = (x / x_scale).to(dtype)\n    return x, x_scale\n\n\n@cuda_only\n@pytest.mark.parametrize(\"M\", [4, 8])\n@pytest.mark.parametrize(\"sort_preproc\", [\"largest\", \"largest_abs\"])\ndef test_sparseNM_dense(M: int, sort_preproc: str) -> None:\n    dtype = torch.bfloat16\n    torch.manual_seed(0)\n    N = 2\n\n    A = torch.randn([128, 128], device=\"cuda\", dtype=dtype)\n    As = torch.ops.xformers.sparseNM_dense(A, N=N, M=M, sort_preproc=sort_preproc)\n    Amask = A.reshape([-1, M])\n    if sort_preproc == \"largest_abs\":\n        Amask = Amask.abs()\n    # NOTE: We want to know which of the 4 values will be masked out after\n    # sparsity. We use 2 argsorts to compute the rank of each element\n    # Example:\n    # [8, 1, 2, 9] < Array values\n    # [1, 2, 0, 3] < After the first `argsort`\n    # [2, 0, 1, 3] < After the second `argsort`\n    #  |  ^ 9 is in index 0 of the sorted array\n    #  ^ 8 is in index 2 of the sorted array\n    Amask = Amask.argsort().argsort()\n    As_ref = A.clone().reshape([-1, M])\n    As_ref[Amask < (M - N)] = 0\n    As_ref = As_ref.reshape_as(A)\n    # NOTE: Sometimes we have ties\n    # [0, 1, 1, 2] can be sparsified as [0, 1, 0, 2] or [0, 0, 1, 2]\n    # Both are valid so there is a small margin for error here\n    assert (As != As_ref).mean(dtype=torch.float).item() < 0.002\n\n\n@requires_h100_s24\ndef test_sparse24_fp8_sm90_cutlass_gemm_eye(\n    M=512, K=256, dtype=torch.float8_e4m3fn\n) -> None:\n    torch.manual_seed(0)\n    A = torch.randn([M, K], device=\"cuda\", dtype=torch.bfloat16)\n    A[A == 0] = 1\n    A = torch.ops.xformers.sparseNM_dense(A, N=2, M=4, sort_preproc=\"largest\")\n    A, _ = _to_fp8_rowwise(A, dtype)\n\n    # NOTE: CUTLASS compression kernel expects the input to be *exactly*\n    # 2:4 sparse already (eg it does not select the largest values)\n    A_packed, A_mdata = torch.ops.xformers._sparse24_sm90_cutlass_compress(A)\n    assert torch.allclose(\n        A_packed.float().sum(), A.float().sum()\n    )  # Check all values are there\n\n    # Check MM without scale\n    eye = torch.eye(A.shape[1], device=A.device, dtype=A.dtype).T\n    A_reconstructed = torch.ops.xformers._sparse24_fp8_sm90_cutlass_gemm(\n        A_packed, A_mdata, eye\n    )\n    assert torch.allclose(A.float(), A_reconstructed.float())\n\n    # Check MM with scale\n    b_scale = torch.randn([1, A.shape[1]], device=eye.device, dtype=torch.float32)\n    a_scale = torch.randn([A.shape[0], 1], device=eye.device, dtype=torch.float32)\n    A_reconstructed = torch.ops.xformers._sparse24_fp8_sm90_cutlass_gemm(\n        A_packed, A_mdata, eye, a_scale=a_scale, b_scale=b_scale\n    )\n    assert torch.allclose(\n        A.float() * b_scale * a_scale, A_reconstructed.float(), rtol=0.01\n    )\n\n\n@requires_h100_s24\ndef test_sparse24_fp8_sm90_cutlass_gemm_random_tensor(\n    M=512, N=1024, K=256, dtype=torch.float8_e4m3fn\n) -> None:\n    torch.manual_seed(0)\n    A = torch.randn([M, K], device=\"cuda\", dtype=torch.bfloat16)\n    A[A == 0] = 1\n    A = torch.ops.xformers.sparseNM_dense(A, N=2, M=4, sort_preproc=\"largest\")\n    A, a_scale = _to_fp8_rowwise(A, dtype)\n    B, b_scale = _to_fp8_rowwise(\n        torch.randn([N, K], device=\"cuda\", dtype=torch.bfloat16), dtype\n    )\n    B = B.T\n    b_scale = b_scale.T\n\n    A_packed, A_mdata = torch.ops.xformers._sparse24_sm90_cutlass_compress(A)\n    out_xformers = torch.ops.xformers._sparse24_fp8_sm90_cutlass_gemm(\n        A_packed, A_mdata, B, a_scale=a_scale, b_scale=b_scale\n    )\n    out_ref = torch._scaled_mm(\n        A, B, scale_a=a_scale, scale_b=b_scale, out_dtype=out_xformers.dtype\n    )\n    assert torch.allclose(out_xformers, out_ref, rtol=0.01, atol=0.01)\n\n\n# end of OSS file\n"
  },
  {
    "path": "tests/test_splitk_reference.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, Tuple\n\nimport pytest\nimport torch\n\nimport xformers.ops\nfrom xformers.ops import fmha\n\nfrom .utils import assert_allclose, disable_tf32, ref_attention_for_test\n\n\n@disable_tf32\ndef ref_attention_splitk_bmhk(\n    q, k, v, attn_bias, scale=None, split_k=None, dtype=None\n) -> torch.Tensor:\n    assert q.ndim == 4\n\n    def T(t):\n        return t.permute((0, 2, 1, 3)).reshape(\n            [t.shape[0] * t.shape[2], t.shape[1], t.shape[3]]\n        )\n\n    if isinstance(attn_bias, xformers.ops.AttentionBias):\n        attn_bias = attn_bias.materialize(\n            (q.shape[0], q.shape[2], q.shape[1], k.shape[1]),\n            device=q.device,\n            dtype=torch.float32,\n        ).reshape([q.shape[0] * q.shape[2], q.shape[1], k.shape[1]])\n    out = ref_attention_splitk(\n        T(q), T(k), T(v), attn_bias, scale=scale, split_k=split_k, dtype=dtype\n    )\n    out = out.reshape([q.shape[0], q.shape[2], q.shape[1], v.shape[3]])\n    return out.permute((0, 2, 1, 3))\n\n\n@disable_tf32\ndef ref_attention_splitk(\n    q, k, v, attn_bias, scale=None, split_k=2, dtype=None\n) -> torch.Tensor:\n    if q.ndim == 5:\n\n        def attn_bias_group(group: int):\n            if isinstance(attn_bias, torch.Tensor):\n                return attn_bias[:, group]\n            if isinstance(attn_bias, fmha.attn_bias.LowerTriangularMaskWithTensorBias):\n                return fmha.attn_bias.LowerTriangularMaskWithTensorBias(\n                    attn_bias._bias[:, group]\n                )\n            return attn_bias\n\n        return torch.stack(\n            [\n                ref_attention_splitk_bmhk(\n                    q[:, :, g],\n                    k[:, :, g],\n                    v[:, :, g],\n                    attn_bias=attn_bias_group(g),\n                    split_k=split_k,\n                    dtype=dtype,\n                )\n                for g in range(q.shape[2])\n            ],\n            dim=2,\n        )\n\n    if q.ndim == 4:\n        return ref_attention_splitk_bmhk(\n            q, k, v, attn_bias=attn_bias, split_k=split_k, dtype=dtype\n        )\n    assert q.ndim == 3\n    if dtype is None:\n        dtype = torch.float32\n    q = q.to(dtype=dtype)\n    k = k.to(dtype=dtype)\n    v = v.to(dtype=dtype)\n\n    if scale is None:\n        scale = q.shape[-1] ** -0.5\n    assert not q.isnan().any()\n    q = q * scale\n    assert not q.isnan().any()\n\n    if attn_bias is not None:\n        if isinstance(attn_bias, xformers.ops.AttentionBias):\n            # Always create in B,H,Mq,Mk format\n            attn_bias_tensor = attn_bias.materialize(\n                (q.shape[0], 1, q.shape[1], k.shape[1]),\n                device=q.device,\n                dtype=torch.float32,\n            )\n        else:\n            attn_bias_tensor = attn_bias\n        if attn_bias_tensor.ndim == 4:\n            assert q.shape[0] == attn_bias_tensor.shape[0] * attn_bias_tensor.shape[1]\n            attn_bias_tensor = attn_bias_tensor.reshape(\n                [-1, *attn_bias_tensor.shape[2:]]\n            )\n\n    split_size = k.size(-2) // split_k\n    split_config = {\"dim\": -2, \"split_size_or_sections\": split_size}\n    k_split = torch.split(k, **split_config)\n    v_split = torch.split(v, **split_config)\n    attn_bias_split = torch.split(\n        attn_bias_tensor, dim=-1, split_size_or_sections=split_size\n    )\n\n    def compute_attention_split(q_whole, k_slice, v_slice, attn_bias_slice):\n        p_slice = q_whole @ k_slice.transpose(-2, -1)\n        p_slice += attn_bias_slice\n        row_max = torch.max(p_slice, dim=-1, keepdim=True).values\n        p_slice_scaled = p_slice - row_max\n        p_slice_scaled[p_slice_scaled.isnan()] = float(\"-inf\")\n        s = torch.exp(p_slice_scaled)\n        row_sumexp = torch.sum(s, dim=-1, keepdim=True)\n        attn_slice = s @ v_slice\n        return {\n            \"attn_slice\": attn_slice,\n            \"row_max\": row_max,\n            \"row_sumexp\": row_sumexp,\n        }\n\n    splits = list(zip(k_split, v_split, attn_bias_split))\n\n    slices = list(map(lambda s: compute_attention_split(q, s[0], s[1], s[2]), splits))\n    out = torch.zeros_like(q)\n\n    # reduce out over split-k slices\n\n    global_max = torch.zeros_like(slices[0][\"row_max\"]).fill_(float(\"-inf\"))\n    global_sumexp = torch.zeros_like(slices[0][\"row_sumexp\"])\n\n    for s in slices:\n        local_out = s[\"attn_slice\"]\n        local_max = s[\"row_max\"]\n        local_sumexp = s[\"row_sumexp\"]\n\n        log_alpha = -torch.abs(local_max - global_max)\n        alpha = torch.exp(log_alpha)\n        alpha.nan_to_num_(1.0)\n\n        pick_new = local_max < global_max\n        new_coef = torch.where(pick_new, alpha, 1.0)\n        curr_coef = torch.where(pick_new, 1.0, alpha)\n\n        out = out * curr_coef + local_out * new_coef\n        global_sumexp = global_sumexp * curr_coef + local_sumexp * new_coef\n        global_max = torch.max(local_max, global_max)\n    out /= global_sumexp\n    return out\n\n\ndef _kv_heads_label(kv_heads: Optional[int]) -> str:\n    if kv_heads is None:\n        return \"\"\n    if kv_heads == 1:\n        return \"mq\"\n    return f\"gqa{kv_heads}\"\n\n\n@pytest.mark.parametrize(\"dtype\", [\"f32\"])\n@pytest.mark.parametrize(\"kv_heads\", [None, 1, 2], ids=_kv_heads_label)\n@pytest.mark.parametrize(\"n_heads\", [16])\n@pytest.mark.parametrize(\"padding, bsz\", [(32, 8), (4096, 1)])\n@pytest.mark.parametrize(\"split_k\", [1, 2, 4])\n@pytest.mark.parametrize(\"device\", [\"cpu\"])\ndef test_splitk_reference(\n    kv_heads: int,\n    n_heads: int,\n    padding: int,\n    bsz: int,\n    dtype: str,\n    device: str,\n    split_k: int,\n):\n    dtype_ = {\"f16\": torch.float16, \"bf16\": torch.bfloat16, \"f32\": torch.float32}[dtype]\n    torch.manual_seed(1)\n    d = 256\n    num_queries = 1\n    if kv_heads is not None and kv_heads > 1:\n        k_shape: Tuple[int, ...] = (1, bsz * padding, kv_heads, n_heads, d)\n        q_shape: Tuple[int, ...] = (\n            1,\n            bsz * num_queries,\n            kv_heads,\n            n_heads,\n            d,\n        )\n    else:\n        k_shape = (1, bsz * padding, n_heads, d)\n        q_shape = (1, bsz * num_queries, n_heads, d)\n\n    k = torch.rand(k_shape, dtype=dtype_, device=device)\n    k_seqlen = torch.randint(1, padding + 1, (bsz,)).tolist()\n    v = torch.rand_like(k)\n    q = torch.rand(q_shape, dtype=dtype_, device=device)\n    causal_diagonal = torch.tensor(  # TODO: make unnecessary\n        [i - 1 for i in k_seqlen], dtype=torch.int32, device=device\n    )\n\n    if kv_heads is not None:\n        k = k[..., :1, :].expand(k_shape)\n        v = v[..., :1, :].expand(k_shape)\n\n    attn_bias = fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[1] * bsz,\n        kv_seqlen=k_seqlen,\n        causal_diagonal=causal_diagonal,\n        kv_padding=padding,\n    )\n    ref_out = ref_attention_for_test(q, k, v, attn_bias)\n    splitk_out = ref_attention_splitk(q, k, v, attn_bias, None, split_k=split_k)\n    assert_allclose(\n        ref_out,\n        splitk_out,\n        atol=fmha.ck.FwOp.ERROR_ATOL[dtype_],\n        rtol=fmha.ck.FwOp.ERROR_RTOL[dtype_],\n    )\n"
  },
  {
    "path": "tests/test_tiled_matmul.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport random\n\nimport pytest\nimport torch\n\nfrom xformers import _is_triton_available\nfrom xformers.ops.tiled_matmul import tiled_matmul\n\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\ncuda_sm70_only = pytest.mark.skipif(\n    compute_capability < (7, 0), reason=\"requires sm70+\"\n)\n\n# We care about correctness, not performance, hence let's \"disable\" the\n# expensive autotuning by removing all configs except one (the first one).\nif _is_triton_available():\n    from xformers.ops._triton.tiled_matmul_kernels import _xformers_tiled_matmul_kernel\n\n    while len(_xformers_tiled_matmul_kernel.configs) > 1:\n        _xformers_tiled_matmul_kernel.configs.pop()\n\n\ndef generate_test_shapes(*repeats, num_shapes=5):\n    shapes = []\n    r = random.Random(0)\n    for repeat in repeats:\n        m_num_tiles, n_num_tiles, k_num_tiles = repeat\n        for _ in range(num_shapes):\n            shapes.append(\n                (\n                    [r.randint(2, 1024 // m_num_tiles) for _ in range(m_num_tiles)],\n                    [r.randint(2, 1024 // n_num_tiles) for _ in range(n_num_tiles)],\n                    [r.randint(2, 1024 // k_num_tiles) for _ in range(k_num_tiles)],\n                )\n            )\n    return shapes\n\n\n_test_shapes = generate_test_shapes((1, 1, 1), (3, 3, 3))\n_dtypes = [torch.float32, torch.bfloat16, torch.float16]\n\n\ndef ceil_of_ratio(n, k):\n    return (n + k - 1) // k\n\n\ndef make_operands(m, n, k, *, dtype):\n    \"\"\"Produce lhs, rhs and reference output tensors\n\n    To dodge numerical accuracy differences between our kernels and PyTorch's\n    ones, we avoid random values and construct matrices whose product is an\n    exact mathematical computation, specifically: the remainder!\n\n    We do it by having the i-th row of lhs and the j-th column on rhs be like:\n    * lhs: i times \"1\", followed by \"0\"\n    * rhs: j-1 times \"1\", followed by \"-(j-1)\", then repeated\n    The running sum of their pointwise product will thus be:\n    1, 2, 3, ..., j-1, 0, 1, 2, 3, ... and so on\n    And the final value will be remainder of i by j.\n\n    If K is smaller than M and/or N, this function also takes care of repeating\n    some rows and/or columns in order to \"fill\" M and/or K. Similarly, if the\n    precision of the dtype is too low to store the result without losses, the\n    function will only use small-enough values, and repeat them as needed.\n\n    Finally, the function permutes the rows and columns, in order to avoid a\n    predictable block structure.\n\n    \"\"\"\n    max_value = min(k, int(1 / torch.finfo(dtype).eps) * 2)\n    m_perm = torch.randperm(m)\n    n_perm = torch.randperm(n)\n\n    num_reps_m = ceil_of_ratio(m, max_value)\n    lhs = (\n        torch.ones((min(m, max_value), k), dtype=dtype)\n        .tril()\n        .repeat([num_reps_m, 1])[m_perm, :]\n    )\n    assert lhs.shape == (m, k)\n\n    num_reps_n = ceil_of_ratio(n, max_value)\n    rhs = torch.ones((k, min(n, max_value)), dtype=dtype)\n    for i in range(2, min(n, max_value) + 2):\n        rhs[:, i - 2][i - 1 :: i] = -i + 1\n    rhs = rhs.repeat([1, num_reps_n])[:, n_perm]\n    assert rhs.shape == (k, n)\n\n    lhs_idxs = torch.arange(1, min(m, max_value) + 1).repeat([num_reps_m])[m_perm, None]\n    rhs_idxs = torch.arange(2, min(n, max_value) + 2).repeat([num_reps_n])[None, n_perm]\n    out = torch.remainder(lhs_idxs, rhs_idxs).to(dtype)\n    assert out.shape == (m, n)\n\n    return lhs, rhs, out\n\n\n@cuda_only\n@cuda_sm70_only\n@pytest.mark.parametrize(\"shape\", _test_shapes, ids=[str(x) for x in _test_shapes])\n@pytest.mark.parametrize(\"dtype\", _dtypes, ids=[str(x) for x in _dtypes])\n@pytest.mark.parametrize(\n    \"compile\", [pytest.param(False, id=\"eager\"), pytest.param(True, id=\"compile\")]\n)\ndef test_forward_backward(\n    shape,\n    dtype,\n    compile: bool,\n):\n    if compile and dtype is torch.bfloat16 and compute_capability < (8, 0):\n        # https://fb.workplace.com/groups/1075192433118967/posts/1480158559289017\n        pytest.skip(\"Dynamo misbehaves on V100 or earlier when handling bf16\")\n\n    m_tiles, n_tiles, k_tiles = shape\n    m, n, k = sum(m_tiles), sum(n_tiles), sum(k_tiles)\n\n    torch.manual_seed(m * n * k)\n    torch._dynamo.reset_code_caches()  # avoids hitting recompilation limit\n\n    a, b, c_reference = make_operands(m, n, k, dtype=dtype)\n    a = a.cuda().requires_grad_()\n    b = b.cuda().requires_grad_()\n    c_reference = c_reference.cuda()\n\n    # In one operand make each tile have its own strides, in the other use the\n    # same stride for all tiles. And make the two operands have the stride==1\n    # in different dimensions.\n    a_tiled = [\n        [y.t().clone().t() for y in x.split(k_tiles, dim=1)]\n        for x in a.split(m_tiles, dim=0)\n    ]\n    b_tiled = [[y for y in x.split(n_tiles, dim=1)] for x in b.split(k_tiles, dim=0)]\n\n    tiled_matmul_compiled = torch.compile(\n        tiled_matmul, fullgraph=True, disable=not compile\n    )\n\n    c_test_tiled = tiled_matmul_compiled(a_tiled, b_tiled)\n    c_test = torch.cat([torch.cat(x, dim=1) for x in c_test_tiled], dim=0)\n\n    torch.testing.assert_close(c_test, c_reference)\n\n    # To avoid numerical issues in the backward, set things up so that we only\n    # multiply by a diagonal matrix whose entries are +/- 2^{-1/0/+1} (so that\n    # it only changes the sign bit and the exponent).\n    diag = torch.tensor(random.choices([-2, -1, -0.5, 0.5, 1, 2], k=min(m, n)))\n    grad_c = torch.zeros_like(c_test)\n    torch.diag(grad_c)[:] = diag\n    grad_a_reference = torch.matmul(grad_c, b.detach().t())\n    grad_b_reference = torch.matmul(a.detach().t(), grad_c)\n\n    torch.autograd.backward([c_test], [grad_c], inputs=[a, b])\n\n    torch.testing.assert_close(a.grad, grad_a_reference)\n    torch.testing.assert_close(b.grad, grad_b_reference)\n"
  },
  {
    "path": "tests/test_tree_attention.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom functools import reduce\nfrom itertools import accumulate\nfrom typing import List, Optional, Tuple, Type\n\nimport pytest\nimport torch\n\nfrom xformers.ops import fmha\nfrom xformers.ops.fmha.attn_bias import (\n    BlockDiagonalPaddedKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n)\nfrom xformers.ops.fmha.common import AttentionFwOpBase\nfrom xformers.ops.tree_attention import (\n    construct_full_tree_choices,\n    construct_tree_choices,\n    tree_attention,\n    TreeAttnMetadata,\n    use_triton_splitk_for_prefix,\n)\nfrom xformers.utils import do_bench_cudagraph\n\ncompute_capability = (0, 0)\nif torch.cuda.is_available():\n    compute_capability = torch.cuda.get_device_capability(\"cuda\")\nsm80_or_better_only = pytest.mark.skipif(\n    compute_capability < (8, 0), reason=\"requires sm80+\"\n)\n\n\n# fmt: off\n# Tree defintions - see doctring of\n# xformers.ops.tree_attention.TreeAttnMetadata.from_tree_choices\n# for the format description\n# from https://github.com/SafeAILab/EAGLE/blob/e98fc7c/model/choices.py\neagle_mc_sim_7b_63 = [(0,),(1,),(2,),(3,),(0,0),(0,1),(0,2),(1,0),(1,1),(2,0),(2,1),(3,0)  # noqa\n                ,(0,0,0),(0,0,1),(0,0,2),(0,1,0),(0,1,1),(0,2,0),(0,2,1),(1,0,0),  # noqa\n                (0,0,0,0),(0,0,0,1),(0,0,0,2),(0,0,0,0,0),(0,0,0,0,1)]  # noqa\n\n# Medusa choices from\n# https://github.com/FasterDecoding/Medusa/blob/5e98053/medusa/model/medusa_choices.py\nmc_sim_7b_63 = [(0,), (0, 0), (1,), (0, 1), (2,), (0, 0, 0), (1, 0), (0, 2), (3,), (0, 3), (4,), (0, 4), (2, 0), (0, 5), (0, 0, 1), (5,), (0, 6), (6,), (0, 7), (0, 1, 0), (1, 1), (7,), (0, 8), (0, 0, 2), (3, 0), (0, 9), (8,), (9,), (1, 0, 0), (0, 2, 0), (1, 2), (0, 0, 3), (4, 0), (2, 1), (0, 0, 4), (0, 0, 5), (0, 0, 0, 0), (0, 1, 1), (0, 0, 6), (0, 3, 0), (5, 0), (1, 3), (0, 0, 7), (0, 0, 8), (0, 0, 9), (6, 0), (0, 4, 0), (1, 4), (7, 0), (0, 1, 2), (2, 0, 0), (3, 1), (2, 2), (8, 0), (0, 5, 0), (1, 5), (1, 0, 1), (0, 2, 1), (9, 0), (0, 6, 0), (0, 0, 0, 1), (1, 6), (0, 7, 0)]  # noqa\nvicuna_7b_stage2 = [(0,), (0, 0), (1,), (0, 1), (0, 0, 0), (1, 0), (2,), (0, 2), (0, 0, 1), (0, 3), (3,), (0, 1, 0), (2, 0), (4,), (0, 0, 2), (0, 4), (1, 1), (1, 0, 0), (0, 0, 0, 0), (5,), (0, 0, 3), (0, 5), (0, 2, 0), (3, 0), (0, 1, 1), (0, 6), (6,), (0, 7), (0, 0, 4), (4, 0), (1, 2), (0, 8), (7,), (0, 3, 0), (0, 0, 0, 1), (0, 0, 5), (2, 1), (0, 0, 6), (1, 0, 1), (0, 0, 1, 0), (2, 0, 0), (5, 0), (0, 9), (0, 1, 2), (8,), (0, 4, 0), (0, 2, 1), (1, 3), (0, 0, 7), (0, 0, 0, 2), (0, 0, 8), (1, 1, 0), (0, 1, 0, 0), (6, 0), (9,), (0, 1, 3), (0, 0, 0, 3), (1, 0, 2), (0, 5, 0), (3, 1), (0, 0, 2, 0), (7, 0), (1, 4)]  # noqa\nvicuna_7b_stage1_ablation = [(0,), (0, 0), (1,), (0, 0, 0), (0, 1), (1, 0), (2,), (0, 2), (0, 0, 1), (3,), (0, 3), (0, 1, 0), (2, 0), (0, 0, 2), (0, 4), (4,), (0, 0, 0, 0), (1, 0, 0), (1, 1), (0, 0, 3), (0, 2, 0), (0, 5), (5,), (3, 0), (0, 1, 1), (0, 6), (6,), (0, 0, 4), (1, 2), (0, 0, 0, 1), (4, 0), (0, 0, 5), (0, 7), (0, 8), (0, 3, 0), (0, 0, 1, 0), (1, 0, 1), (7,), (2, 0, 0), (0, 0, 6), (2, 1), (0, 1, 2), (5, 0), (0, 2, 1), (0, 9), (0, 0, 0, 2), (0, 4, 0), (8,), (1, 3), (0, 0, 7), (0, 1, 0, 0), (1, 1, 0), (6, 0), (9,), (0, 0, 8), (0, 0, 9), (0, 5, 0), (0, 0, 2, 0), (1, 0, 2), (0, 1, 3), (0, 0, 0, 3), (3, 0, 0), (3, 1)]  # noqa\nvicuna_7b_stage1 = [(0,), (0, 0), (1,), (2,), (0, 1), (1, 0), (3,), (0, 2), (4,), (0, 0, 0), (0, 3), (5,), (2, 0), (0, 4), (6,), (0, 5), (1, 1), (0, 0, 1), (7,), (3, 0), (0, 6), (8,), (9,), (0, 1, 0), (0, 7), (0, 8), (4, 0), (0, 0, 2), (1, 2), (0, 9), (2, 1), (5, 0), (1, 0, 0), (0, 0, 3), (1, 3), (0, 2, 0), (0, 1, 1), (0, 0, 4), (6, 0), (1, 4), (0, 0, 5), (2, 2), (0, 3, 0), (3, 1), (0, 0, 6), (7, 0), (1, 5), (1, 0, 1), (2, 0, 0), (0, 0, 7), (8, 0), (0, 0, 0, 0), (4, 1), (0, 1, 2), (0, 4, 0), (9, 0), (0, 2, 1), (2, 3), (1, 6), (0, 0, 8), (0, 5, 0), (3, 2), (5, 1)]  # noqa\nvicuna_13b_stage2 = [(0,), (0, 0), (1,), (0, 0, 0), (0, 1), (1, 0), (2,), (0, 2), (0, 0, 1), (0, 1, 0), (3,), (0, 3), (2, 0), (0, 0, 2), (0, 0, 0, 0), (0, 4), (1, 0, 0), (1, 1), (4,), (0, 0, 3), (0, 5), (0, 2, 0), (5,), (3, 0), (0, 1, 1), (0, 6), (0, 0, 4), (0, 0, 0, 1), (0, 7), (0, 0, 5), (1, 2), (0, 0, 1, 0), (0, 3, 0), (1, 0, 1), (4, 0), (0, 0, 6), (0, 8), (2, 0, 0), (0, 9), (6,), (7,), (2, 1), (5, 0), (0, 1, 2), (0, 0, 0, 2), (8,), (0, 4, 0), (0, 1, 0, 0), (0, 2, 1), (0, 0, 7), (1, 1, 0), (1, 3), (0, 0, 2, 0), (9,), (0, 0, 8), (0, 5, 0), (0, 0, 0, 3), (0, 0, 9), (0, 1, 3), (1, 0, 2), (0, 0, 1, 1), (3, 0, 0), (1, 0, 0, 0)]  # noqa\nvicuna_13b_stage1 = [(0,), (0, 0), (1,), (0, 1), (2,), (1, 0), (0, 0, 0), (0, 2), (3,), (0, 3), (4,), (2, 0), (0, 4), (0, 0, 1), (0, 5), (5,), (1, 1), (0, 1, 0), (6,), (0, 6), (0, 0, 2), (7,), (3, 0), (8,), (0, 7), (0, 8), (1, 0, 0), (0, 0, 3), (4, 0), (1, 2), (9,), (0, 9), (2, 1), (0, 2, 0), (0, 0, 4), (1, 3), (0, 1, 1), (0, 0, 5), (5, 0), (0, 3, 0), (0, 0, 0, 0), (0, 0, 6), (6, 0), (1, 4), (2, 0, 0), (0, 1, 2), (3, 1), (0, 4, 0), (1, 0, 1), (2, 2), (0, 0, 7), (1, 5), (7, 0), (0, 0, 8), (8, 0), (0, 5, 0), (0, 0, 9), (0, 2, 1), (1, 1, 0), (0, 1, 3), (4, 1), (2, 3), (1, 6)]  # noqa\nvicuna_33b_stage2 = [(0,), (0, 0), (1,), (0, 1), (0, 0, 0), (1, 0), (2,), (0, 2), (0, 0, 1), (0, 3), (3,), (0, 1, 0), (2, 0), (0, 4), (4,), (0, 0, 2), (1, 1), (1, 0, 0), (0, 5), (5,), (0, 0, 0, 0), (0, 0, 3), (3, 0), (0, 2, 0), (0, 6), (0, 1, 1), (6,), (0, 0, 4), (0, 7), (7,), (1, 2), (4, 0), (8,), (0, 3, 0), (0, 0, 5), (0, 0, 0, 1), (0, 8), (2, 1), (0, 9), (1, 0, 1), (2, 0, 0), (0, 0, 6), (5, 0), (0, 0, 1, 0), (1, 3), (0, 1, 2), (0, 4, 0), (0, 0, 7), (0, 2, 1), (9,), (1, 1, 0), (0, 0, 0, 2), (6, 0), (0, 0, 8), (0, 1, 0, 0), (7, 0), (0, 1, 3), (0, 5, 0), (1, 4), (0, 0, 9), (3, 1), (1, 0, 2), (2, 2)]  # noqa\nvicuna_33b_stage1 = [(0,), (1,), (0, 0), (2,), (0, 1), (3,), (1, 0), (4,), (0, 2), (5,), (0, 3), (0, 0, 0), (6,), (0, 4), (2, 0), (7,), (1, 1), (0, 5), (3, 0), (8,), (9,), (0, 6), (0, 7), (0, 0, 1), (1, 2), (4, 0), (0, 1, 0), (0, 8), (0, 9), (2, 1), (0, 0, 2), (5, 0), (1, 3), (0, 0, 3), (1, 0, 0), (1, 4), (6, 0), (0, 2, 0), (3, 1), (2, 2), (0, 0, 4), (7, 0), (0, 1, 1), (1, 5), (4, 1), (0, 0, 5), (0, 3, 0), (9, 0), (8, 0), (1, 6), (0, 0, 6), (2, 3), (0, 1, 2), (3, 2), (0, 4, 0), (2, 0, 0), (1, 7), (1, 0, 1), (0, 0, 7), (5, 1), (2, 4), (0, 0, 8), (0, 2, 1)]  # noqa\nzephyr_stage2 = [(0,), (0, 0), (1,), (0, 1), (2,), (0, 0, 0), (1, 0), (0, 2), (3,), (0, 3), (4,), (2, 0), (0, 0, 1), (0, 4), (5,), (0, 5), (0, 1, 0), (1, 1), (6,), (0, 0, 2), (3, 0), (0, 6), (7,), (0, 7), (0, 8), (0, 0, 3), (1, 0, 0), (0, 9), (0, 2, 0), (1, 2), (4, 0), (8,), (9,), (2, 1), (0, 1, 1), (0, 0, 4), (0, 0, 0, 0), (5, 0), (0, 3, 0), (1, 3), (0, 0, 5), (0, 0, 6), (6, 0), (2, 0, 0), (1, 0, 1), (0, 1, 2), (0, 4, 0), (1, 4), (3, 1), (2, 2), (0, 0, 7), (7, 0), (0, 2, 1), (0, 0, 8), (0, 1, 3), (0, 5, 0), (1, 5), (0, 0, 9), (1, 1, 0), (0, 0, 0, 1), (0, 0, 1, 0), (4, 1), (2, 3)]  # noqa\n\nmedusa_choices = [\n    mc_sim_7b_63,\n    vicuna_7b_stage2,\n    vicuna_7b_stage1_ablation,\n    vicuna_7b_stage1,\n    vicuna_13b_stage2,\n    vicuna_13b_stage1,\n    vicuna_33b_stage2,\n    vicuna_33b_stage1,\n    zephyr_stage2,\n]\n\n# fmt: on\n\n\n@sm80_or_better_only\n@pytest.mark.parametrize(\n    \"tree_choices\",\n    [\n        eagle_mc_sim_7b_63,\n        *medusa_choices,\n        construct_full_tree_choices(1, 1),\n        construct_full_tree_choices(1, 4),\n        construct_full_tree_choices(4, 1),\n        construct_full_tree_choices(3, 2),\n        construct_full_tree_choices(2, 3),\n        construct_full_tree_choices(5, 3),\n        construct_full_tree_choices(10, 2),\n    ],\n)\n@pytest.mark.parametrize(\"B\", [1, 16])\n@pytest.mark.parametrize(\"G\", [1, 2])\n@pytest.mark.parametrize(\"square\", [True, False])\n@pytest.mark.parametrize(\"paged\", [True, False])\n@torch.no_grad()\ndef test_tree_attention(\n    tree_choices: List[Tuple[int, ...]], B: int, G: int, square: bool, paged: bool\n) -> None:\n    H = 8\n    D = 128\n    Mk = 8192\n    dtype = torch.bfloat16\n    tree_size_q = len(tree_choices) + 1 if square else max(len(tree_choices) // 2, 1)\n    run_tree_attention_inner(\n        tree_choices, B, Mk, G, H, D, tree_size_q, dtype, paged=paged\n    )\n\n\nclass SplitKAutotune(fmha.triton_splitk.FwOp):\n    AUTOTUNE = True\n\n\ndef run_tree_attention_inner(\n    tree_choices: List[Tuple[int, ...]],\n    B: int,\n    Mk: int,\n    G: int,\n    H: int,\n    D: int,\n    tree_size_q: int,\n    dtype: torch.dtype,\n    benchmark: bool = False,\n    autotune: bool = False,\n    randomize_lengths: bool = True,\n    paged: bool = False,\n) -> None:\n    \"\"\"\n    Test Medusa-style tree attention.\n    \"\"\"\n    if (\n        paged\n        and PagedBlockDiagonalPaddedKeysMask\n        not in SplitKAutotune.SUPPORTED_ATTN_BIAS_TYPES\n    ):\n        pytest.skip(\"Does not supported paged attention bias\")\n\n    torch.manual_seed(0)\n    # + 1 comes from the root node\n    tree_size_kv = len(tree_choices) + 1\n\n    # Simulate output of speculative heads\n    q_full = torch.randn([B, tree_size_kv, G, H, D], device=\"cuda\", dtype=dtype)\n    q = q_full[:, -tree_size_q:].clone()\n    spec_k = torch.randn([B, tree_size_kv, G, 1, D], device=\"cuda\", dtype=dtype)\n    spec_v = torch.randn_like(spec_k)\n    spec_k = spec_k.expand(-1, -1, -1, H, -1)\n    spec_v = spec_v.expand(-1, -1, -1, H, -1)\n\n    # Create K/V cache before the speculative tokens\n    cache_k = torch.randn([B, Mk, G, 1, D], device=\"cuda\", dtype=dtype)\n    cache_v = torch.randn_like(cache_k)\n    cache_k = cache_k.expand(-1, -1, -1, H, -1)\n    cache_v = cache_v.expand(-1, -1, -1, H, -1)\n\n    if randomize_lengths:\n        kv_lens_device = torch.randint(1, Mk + 1, size=(B,), device=q.device)\n    else:\n        kv_lens_device = torch.full(size=(B,), fill_value=Mk, device=q.device)\n    kv_lens = kv_lens_device.tolist()\n\n    triton_splitk_op = SplitKAutotune if autotune else fmha.triton_splitk.FwOp\n\n    # Compute attention on the full context using merge_attentions: it will use\n    # padded non-causal block-diagonal on the first part (before spec tokens) and\n    # explicit attn_bias mask on the second part (spec tokens)\n    attn = tree_attention_with_sync(\n        q,\n        spec_k,\n        spec_v,\n        cache_k,\n        cache_v,\n        tree_choices,\n        kv_lens,\n        prefix_op=triton_splitk_op,\n        suffix_op=triton_splitk_op,\n        paged=paged,\n    )\n\n    tree_attn_metadata = TreeAttnMetadata.from_tree_choices(\n        tree_choices, q.dtype, q.device\n    )\n\n    t_optimized_ms_fa = 0.0\n    t_optimized_ms_triton = 0.0\n    if benchmark:\n        # Construct attention bias for \"left\" part of the attention - the exising K/V context\n        prefix_attn_bias = fmha.attn_bias.BlockDiagonalPaddedKeysMask.from_seqlens(\n            q_seqlen=[tree_size_q for _ in range(B)], kv_seqlen=kv_lens, kv_padding=Mk\n        )\n        # Create an explit attention bias for the speculative part of the attention\n        spec_attn_bias = tree_attn_metadata.attention_bias\n\n        torch.cuda.synchronize()\n        bench_stream = torch.cuda.Stream()\n        with torch.cuda.stream(bench_stream):\n            t_optimized_ms_triton = do_bench_cudagraph(\n                lambda: tree_attention(\n                    q,\n                    spec_k,\n                    spec_v,\n                    cache_k,\n                    cache_v,\n                    spec_attn_bias,\n                    prefix_attn_bias,\n                    prefix_op=triton_splitk_op,\n                    suffix_op=triton_splitk_op,\n                )\n            )\n\n        torch.cuda.synchronize()\n        bench_stream = torch.cuda.Stream()\n        with torch.cuda.stream(bench_stream):\n            t_optimized_ms_fa = do_bench_cudagraph(\n                lambda: tree_attention(\n                    q,\n                    spec_k,\n                    spec_v,\n                    cache_k,\n                    cache_v,\n                    spec_attn_bias,\n                    prefix_attn_bias,\n                    prefix_op=fmha.flash.FwOp if torch.version.cuda else fmha.ck.FwOp,\n                    suffix_op=triton_splitk_op,\n                )\n            )\n\n    # Compute attention on the full context in a slow way, unrolling every path in the tree\n    paths_w_unpadded_indices = [\n        path.tolist()[:path_len]\n        for path, path_len in zip(\n            tree_attn_metadata.retrieval_indices, tree_attn_metadata.path_lengths\n        )\n    ]\n\n    # Reference implementation, which does path unrolling, will compute attention on the full query\n    # of length tree_size_kv > tree_size_q.\n    # Then we'll pick only relevant elements to compare with the optimized implementation.\n    paths_w_unpadded_indices_q = [\n        [x - tree_size_kv + tree_size_q for x in path]\n        for path in paths_w_unpadded_indices\n    ]\n    paths_w_unpadded_indices_q_mask = [\n        x >= 0 for path in paths_w_unpadded_indices_q for x in path\n    ]\n\n    paths_w_unpadded_indices_q = [\n        [x for x in path if x >= 0] for path in paths_w_unpadded_indices_q\n    ]\n    attn_unrolled = torch.cat(\n        [attn[:, path, :, :] for path in paths_w_unpadded_indices_q], dim=1\n    )\n    ref_attn_full = ref_tree_attention(\n        q_full, spec_k, spec_v, cache_k, cache_v, paths_w_unpadded_indices, kv_lens\n    )\n    ref_attn = ref_attn_full[:, paths_w_unpadded_indices_q_mask, :, :]\n\n    if benchmark:\n        # Here we compute vanilla attention with the the shapes similar to the tree attention we benchmarked above.\n        # The output of this vanilla attention will be different - we are just measuring the runtime to get\n        # some order of magnitude estimation of runtime.\n        torch.cuda.synchronize()\n        attn_bias_ref = (\n            fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n                q_seqlen=[tree_size_q for _ in range(B)],\n                kv_seqlen=kv_lens,\n                kv_padding=Mk,\n            )\n        )\n        bench_stream = torch.cuda.Stream()\n        with torch.cuda.stream(bench_stream):\n            t_ref_ms = do_bench_cudagraph(\n                lambda: fmha.memory_efficient_attention_forward(\n                    q.view(1, B * tree_size_q, G, H, D),\n                    cache_k.view(1, B * Mk, G, H, D),\n                    cache_v.view(1, B * Mk, G, H, D),\n                    attn_bias=attn_bias_ref,\n                )\n            )\n\n        gap = (1 - t_ref_ms / min(t_optimized_ms_fa, t_optimized_ms_triton)) * 100\n        triton_faster = t_optimized_ms_fa > t_optimized_ms_triton\n        triton_chosen = torch.version.hip is not None or use_triton_splitk_for_prefix(\n            B, G, tree_size_kv\n        )\n        print(\n            f\"{B=}, {G=}, {Mk=}, {tree_size_q=}, {tree_size_kv=}: \"\n            f\"Tree attention with FA2/CK took {t_optimized_ms_fa * 1e3:.1f}us, \"\n            f\"with Triton Split-K took {t_optimized_ms_triton * 1e3:.1f}us, \"\n            f\"vanilla attention took {t_ref_ms * 1e3:.1f}us, gap {gap:.2f}%. \"\n            f\"Triton faster: {triton_faster}. \"\n            f\"Choice optimal: {triton_chosen == triton_faster}\"\n        )\n\n    torch.testing.assert_close(attn_unrolled, ref_attn, atol=1e-2, rtol=3e-3)\n\n\ndef ref_tree_attention(\n    q: torch.Tensor,\n    spec_k: torch.Tensor,\n    spec_v: torch.Tensor,\n    cache_k: torch.Tensor,\n    cache_v: torch.Tensor,\n    paths_w_unpadded_indices: List[List[int]],\n    kv_lens: List[int],\n) -> torch.Tensor:\n\n    attns = []\n\n    B, _, G, H, D = q.shape\n\n    # Compute attention for every path separately and then concatenate\n\n    for path in paths_w_unpadded_indices:\n        q_path = q[:, path, ...]\n\n        extra_k = torch.empty_like(spec_k[:, path, ...])\n        extra_v = torch.empty_like(extra_k)\n\n        full_k = torch.cat([cache_k, extra_k], dim=1)\n        full_v = torch.cat([cache_v, extra_v], dim=1)\n\n        tree_depth = len(path)\n\n        for b in range(B):\n            full_k[b, kv_lens[b] : kv_lens[b] + tree_depth] = spec_k[b, path]\n            full_v[b, kv_lens[b] : kv_lens[b] + tree_depth] = spec_v[b, path]\n\n        Mk = full_k.shape[1]\n        attn_bias = (\n            fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n                q_seqlen=[tree_depth for _ in range(B)],\n                kv_seqlen=[s + tree_depth for s in kv_lens],\n                kv_padding=Mk,\n            )\n        )\n        attn_path = fmha.memory_efficient_attention_forward(\n            q_path.view(1, B * tree_depth, G, H, D),\n            full_k.view(1, B * Mk, G, H, D),\n            full_v.view(1, B * Mk, G, H, D),\n            attn_bias=attn_bias,\n        )\n        attn_path = attn_path.reshape(B, tree_depth, G, H, D)\n\n        attns.append(attn_path)\n\n    attn = torch.cat(attns, dim=1)\n\n    return attn\n\n\ndef tree_attention_with_sync(\n    q: torch.Tensor,\n    spec_k: torch.Tensor,\n    spec_v: torch.Tensor,\n    cache_k: torch.Tensor,\n    cache_v: torch.Tensor,\n    tree_choices: List[Tuple[int, ...]],\n    kv_lens: List[int],\n    prefix_op: Optional[Type[AttentionFwOpBase]] = None,\n    suffix_op: Optional[Type[AttentionFwOpBase]] = None,\n    paged: bool = False,\n) -> torch.Tensor:\n    \"\"\"\n    A wrapper around tree_attention which constructs the biases.\n    Arguments are the same as in xformers.ops.tree_attention.tree_attention, but instead of\n    spec_attn_bias and prefix_attn_bias this function takes in tree definition in the form of tree_choices\n    and K/V sequence lengths kv_lens.\n    For the format of tree_choices see docstring of\n    xformers.ops.tree_attention.TreeAttnMetadata.from_tree_choices.\n    \"\"\"\n    B, tree_size_q = q.shape[:2]\n    Mk = cache_k.shape[1]\n\n    # attn_prefix ~ (B, Mk, H, D)\n    prefix_attn_bias = BlockDiagonalPaddedKeysMask.from_seqlens(\n        q_seqlen=[tree_size_q for _ in range(B)], kv_seqlen=kv_lens, kv_padding=Mk\n    )\n\n    if paged:\n        # Create a paged K/V cache by randomly permuting blocks and storing the permutation in block_tables.\n        page_size = 256\n        assert Mk % page_size == 0\n        max_blocks_per_row = Mk // page_size\n\n        block_tables = torch.randperm(\n            B * max_blocks_per_row, device=q.device, dtype=torch.int32\n        ).view(B, max_blocks_per_row)\n\n        cache_v = cache_v.clone()\n        cache_k = cache_k.clone()\n\n        v_view = cache_v.view(1, -1, page_size, *cache_v.shape[2:])\n        k_view = cache_k.view(1, -1, page_size, *cache_k.shape[2:])\n\n        block_tables_expanded = (\n            block_tables.to(torch.int64)\n            .flatten()[None, :, None, None, None, None]\n            .expand(v_view.shape)\n        )\n        v_view.scatter_(1, block_tables_expanded, v_view.clone())\n        k_view.scatter_(1, block_tables_expanded, k_view.clone())\n\n        cache_k = k_view.view(1, -1, *cache_k.shape[2:])\n        cache_v = v_view.view(1, -1, *cache_k.shape[2:])\n\n        prefix_attn_bias = prefix_attn_bias.make_paged(  # type: ignore\n            block_tables,\n            page_size=page_size,\n            paged_type=PagedBlockDiagonalPaddedKeysMask,\n        )\n\n    # Create an explit attention bias for the speculative part of the attention\n    spec_attn_bias = TreeAttnMetadata.from_tree_choices(\n        tree_choices, q.dtype, q.device\n    ).attention_bias\n    return tree_attention(\n        q,\n        spec_k,\n        spec_v,\n        cache_k,\n        cache_v,\n        spec_attn_bias[-tree_size_q:],\n        prefix_attn_bias,\n        prefix_op,\n        suffix_op,\n    )\n\n\n@pytest.mark.parametrize(\"depth\", [1, 2, 3, 4])\n@pytest.mark.parametrize(\"branching\", [1, 2, 3, 4])\ndef test_tree_attention_metadata_full_tree(depth: int, branching: int) -> None:\n    \"\"\"\n    Here we test that fields tree_seq_position_ids, tree_indices, and path_lengths\n    are constructed correctly; attention_bias and retrieval_indices are tested in test_tree_attention.\n    \"\"\"\n    tree_choices = construct_full_tree_choices(depth, branching)\n    tree_attn_metadata = TreeAttnMetadata.from_tree_choices(tree_choices)\n\n    num_paths = branching**depth\n    assert len(tree_attn_metadata.path_lengths) == num_paths\n    assert all(length == depth + 1 for length in tree_attn_metadata.path_lengths)\n\n    seq_pos = []\n    for i in range(depth + 1):\n        seq_pos.extend([i] * branching**i)\n\n    assert seq_pos == tree_attn_metadata.tree_seq_position_ids.tolist()\n\n    tree_indices = []\n    for i in range(depth):\n        tree_indices.extend(\n            [i * branching + j + 1 for j in range(branching)] * branching**i\n        )\n\n    assert [0] + tree_indices == tree_attn_metadata.tree_indices.tolist()\n\n    tree_size = len(tree_choices) + 1\n    ref_child_node_indices = torch.arange(tree_size * branching).reshape(\n        tree_size, branching\n    )\n    ref_child_node_indices[ref_child_node_indices >= tree_size - 1] = 0\n    torch.testing.assert_allclose(\n        tree_attn_metadata.child_node_indices, ref_child_node_indices\n    )\n\n    ref_num_children_per_node = torch.full_like(\n        tree_attn_metadata.tree_indices, fill_value=branching\n    )\n    ref_num_children_per_node[-num_paths:] = 0\n    torch.testing.assert_close(\n        tree_attn_metadata.num_children_per_node, ref_num_children_per_node\n    )\n    ref_num_nodes_per_level = torch.tensor([branching**i for i in range(depth + 1)])\n    torch.testing.assert_close(\n        tree_attn_metadata.num_nodes_per_level, ref_num_nodes_per_level\n    )\n\n    ref_subtree_sizes = (\n        torch.tensor([branching**i for i in range(depth + 1)]).cumsum(dim=0).tolist()\n    )\n    assert all(\n        subtree_size == subtree_sizes_ref\n        for subtree_size, subtree_sizes_ref in zip(\n            tree_attn_metadata.subtree_sizes, ref_subtree_sizes\n        )\n    )\n\n    ref_num_children_per_node_at_level: List[torch.Tensor] = [\n        torch.tensor([branching if i < depth else 0] * (branching**i))\n        for i in range(0, depth + 1)\n    ]\n    assert all(\n        torch.allclose(num_children, ref_num_children)\n        for num_children, ref_num_children in zip(\n            tree_attn_metadata.num_children_per_node_at_level,\n            ref_num_children_per_node_at_level,\n        )\n    )\n\n\n@pytest.mark.parametrize(\"branching\", [[2, 3], [2, 3, 2], [2] * 3, [4, 3, 2, 1]])\ndef test_tree_attention_metadata_arbitrary_tree(branching: List[int]) -> None:\n    tree_choices = construct_tree_choices(branching)\n    tree_attn_metadata = TreeAttnMetadata.from_tree_choices(tree_choices)\n    assert all(\n        length == len(branching) + 1 for length in tree_attn_metadata.path_lengths\n    )\n\n    num_paths = reduce(lambda x, y: x * y, branching)\n    assert len(tree_attn_metadata.path_lengths) == num_paths\n\n    num_nodes_per_level = [*accumulate(branching, lambda x, y: x * y)]\n    seq_pos = []\n    for i in range(len(num_nodes_per_level)):\n        seq_pos.extend([i + 1] * num_nodes_per_level[i])\n    assert [0] + seq_pos == tree_attn_metadata.tree_seq_position_ids.tolist()\n\n    tree_indices = []\n    start_idx = 1\n    num_children_per_node_ref = []\n    num_children_per_node_at_level_ref: List[torch.Tensor] = []\n    subtree_sizes_ref: List[int] = []\n    total_num_nodes = 0\n    for i in range(len(branching)):\n        num_nodes_prev_level = 1 if i == 0 else num_nodes_per_level[i - 1]\n        tree_indices.extend(\n            [start_idx + j for j in range(branching[i])] * num_nodes_prev_level\n        )\n        start_idx += branching[i]\n        num_children_per_node_ref.extend([branching[i]] * num_nodes_prev_level)\n        num_children_per_node_at_level_ref.append(\n            torch.tensor([branching[i]] * num_nodes_prev_level)\n        )\n        total_num_nodes += num_nodes_prev_level\n        subtree_sizes_ref.append(total_num_nodes)\n    num_children_per_node_at_level_ref.append(\n        torch.tensor([0] * num_nodes_per_level[-1])\n    )\n    subtree_sizes_ref.append(len(tree_indices) + 1)\n    assert [\n        0\n    ] + tree_indices == tree_attn_metadata.tree_indices.tolist(), (\n        f\"{tree_indices=} {tree_attn_metadata.tree_indices.tolist()=}\"\n    )\n\n    num_children_per_node_ref.extend([0] * num_paths)\n    num_children_per_node_ref_tensor = torch.tensor(num_children_per_node_ref)\n\n    assert all(\n        torch.allclose(num_children, num_children_ref)\n        for num_children, num_children_ref in zip(\n            tree_attn_metadata.num_children_per_node_at_level,\n            num_children_per_node_at_level_ref,\n        )\n    )\n    assert all(\n        subtree_size == subtree_sizes_ref\n        for subtree_size, subtree_sizes_ref in zip(\n            tree_attn_metadata.subtree_sizes, subtree_sizes_ref\n        )\n    )\n    torch.testing.assert_close(\n        num_children_per_node_ref_tensor, tree_attn_metadata.num_children_per_node\n    )\n    torch.testing.assert_close(\n        tree_attn_metadata.num_nodes_per_level,\n        torch.tensor([1] + num_nodes_per_level),\n    )\n    assert tree_attn_metadata.child_node_indices.shape == (\n        len(tree_indices) + 1,\n        max(branching),\n    )\n"
  },
  {
    "path": "tests/test_triton_varargs.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport ast\nimport logging\nimport sys\nfrom typing import Any, List\n\nimport pytest\nimport torch\n\nimport xformers\n\ntry:\n    import triton\n    import triton.language as tl\n\n    from xformers.triton.vararg_kernel import (\n        _VisitorConditionalKernel,\n        unroll_varargs,\n        VarargMode,\n    )\n\n    _triton_available = xformers._is_triton_available()\nexcept ImportError as e:\n    logging.warning(\n        f\"Triton is not available, some optimizations will not be tested.\\n{e}\"\n    )\n    _triton_available = False\n\nenable_tests = (\n    (sys.version_info.major, sys.version_info.minor) >= (3, 9)\n    and _triton_available\n    and torch.cuda.is_available()\n)\n\n\n@pytest.mark.skipif(not enable_tests, reason=\"moe not supported\")\ndef test_triton_varargs_kernel():\n    @triton.jit\n    def sumN(output_ptr, scaling_ptr, *inputs, BLOCK_SIZE: tl.constexpr):\n        offset = tl.arange(0, BLOCK_SIZE)\n        output = tl.zeros([BLOCK_SIZE], tl.float32)\n        scaling: \"VAR_ARGS_ARRAY\"  # type: ignore # noqa: F821\n        for i in range(len(scaling)):\n            scaling[i] = tl.load(scaling_ptr + i)\n\n        for i in range(2):\n            for j in range(len(inputs)):\n                output = output + tl.load(inputs[j] + offset) * scaling[j]\n        tl.store(output_ptr + offset, output)\n\n    BLOCK_SIZE = 32\n    NUM_INPUTS = 2\n    torch.manual_seed(0)\n    inputs = [\n        torch.randn([BLOCK_SIZE], dtype=torch.float32, device=\"cuda\")\n        for _ in range(NUM_INPUTS)\n    ]\n    output = torch.randn([BLOCK_SIZE], dtype=torch.float32, device=\"cuda\")\n    scaling = torch.randn([NUM_INPUTS, 1], dtype=torch.float32, device=\"cuda\")\n    sumN_unrolled = unroll_varargs(sumN, N=NUM_INPUTS)\n    sumN_unrolled[(1,)](output, scaling, *inputs, BLOCK_SIZE=32)\n    assert torch.allclose((2 * torch.stack(inputs) * scaling).sum(0), output)\n\n\n@pytest.mark.skipif(not enable_tests, reason=\"moe not supported\")\n@pytest.mark.parametrize(\"conditional\", [True, False])\ndef test_triton_multiple_varargs_kernel(conditional: bool):\n    @triton.jit\n    def weighted_sumN(\n        output_ptr,\n        a_ptr: \"VAR_ARGS_ARRAY\",  # type: ignore # noqa: F821\n        b: \"VAR_ARGS_ARRAY\",  # type: ignore # noqa: F821\n        BLOCK_SIZE: tl.constexpr,\n    ):\n        # Weighted sum, where the weights are on CPU\n        offset = tl.arange(0, BLOCK_SIZE)\n        output = tl.zeros([BLOCK_SIZE], tl.float32)\n\n        for i in range(len(a_ptr)):\n            output = output + tl.load(a_ptr[i] + offset) * b[i]\n        tl.store(output_ptr + offset, output)\n\n    BLOCK_SIZE = 32\n    NUM_INPUTS = 2\n    torch.manual_seed(0)\n    a = [\n        torch.randn([BLOCK_SIZE], dtype=torch.float32, device=\"cuda\")\n        for _ in range(NUM_INPUTS)\n    ]\n    b = [torch.randn([], dtype=torch.float32, device=\"cuda\") for _ in range(NUM_INPUTS)]\n    b_list = [x.item() for x in b]\n    output = torch.randn([BLOCK_SIZE], dtype=torch.float32, device=\"cuda\")\n    if conditional:\n        kernel = unroll_varargs(\n            weighted_sumN, N=NUM_INPUTS, mode=VarargMode.CONDITIONAL\n        )\n    else:\n        kernel = unroll_varargs(weighted_sumN, N=NUM_INPUTS)\n    kernel[(1,)](output, *a, *b_list, BLOCK_SIZE=32)\n    expected_output = (torch.stack(a) * torch.stack(b).unsqueeze(1)).sum(0)\n    assert torch.allclose(expected_output, output)\n\n\n@pytest.mark.skipif(not enable_tests, reason=\"moe not supported\")\ndef test_triton_varargs_conditional():\n    # to make linter happy\n    VAR_ARGS_ARRAY = List[Any]\n\n    @triton.jit\n    def kernel(\n        x_ptrs: \"VAR_ARGS_ARRAY\",  # noqa: F821\n        y_ptrs: \"VAR_ARGS_ARRAY\",  # noqa: F821\n        numel,\n        BLOCK_SIZE: tl.constexpr,\n    ):\n        pid = tl.program_id(axis=0)\n        offsets = BLOCK_SIZE * pid + tl.arange(0, BLOCK_SIZE)\n        mask = offsets < numel\n        for i in range(len(x_ptrs)):\n            x_ptr = x_ptrs[i]\n            y_ptr = y_ptrs[i]\n\n            data = tl.load(x_ptr + offsets, mask)\n            result = data * data\n            tl.store(y_ptr + offsets, result, mask)\n\n    k = triton.JITFunction(kernel.fn)\n    parsed = ast.parse(k.src)\n    visitor = _VisitorConditionalKernel(N=3)\n    parsed = visitor.visit(parsed)\n    parsed = ast.fix_missing_locations(parsed)\n    new_src = ast.unparse(parsed)  # type: ignore\n\n    assert \"x_ptrs0, x_ptrs1, x_ptrs2\" in new_src\n    assert \"y_ptrs0, y_ptrs1, y_ptrs2\", new_src\n    assert \"for i in range(3):\" in new_src\n    assert \"x_ptr = x_ptrs0 if i == 0 else x_ptrs1 if i == 1 else x_ptrs2\" in new_src\n    assert \"y_ptr = y_ptrs0 if i == 0 else y_ptrs1 if i == 1 else y_ptrs2\" in new_src\n\n\n@pytest.mark.skipif(not enable_tests, reason=\"moe not supported\")\ndef test_subscripting_call():\n    @triton.jit\n    def fused_group_contiguous_nan_clamp_copied_from_inductor(\n        _group_a_ptrs: \"VAR_ARGS_ARRAY\",  # type: ignore # noqa: F821\n        XBLOCK: tl.constexpr,\n    ):\n        xoffset = tl.program_id(0) * XBLOCK\n        xoffset + tl.arange(0, XBLOCK)[:]\n\n    unroll_varargs(\n        fused_group_contiguous_nan_clamp_copied_from_inductor,\n        N=2,\n        mode=VarargMode.CONDITIONAL,\n    )\n"
  },
  {
    "path": "tests/test_unbind.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport random\n\nimport pytest\nimport torch\n\nimport xformers.ops\nfrom xformers.ops.common import _get_storage_base\n\n\n@pytest.mark.parametrize(\"contiguous\", [True, False])\n@pytest.mark.parametrize(\"dim\", [0, 1, 2, 3, 4])\ndef test_unbind(dim: int, contiguous: bool):\n    x = torch.randn([10, 20, 4, 10, 3])\n    x2 = x.clone()\n\n    if not contiguous:\n        perm = list(range(x.ndim))\n        random.Random(dim).shuffle(perm)\n        # Let's hope we didn't pick identity\n        x = x.permute(perm)\n        x2 = x2.permute(perm)\n    assert contiguous == x.is_contiguous()\n    x.requires_grad_(True)\n    x2.requires_grad_(True)\n\n    # FW\n    tensors = xformers.ops.unbind(x, dim)\n    tensors2 = torch.unbind(x2, dim)\n    assert len(tensors) == len(tensors2)\n    for t1, t2 in zip(tensors, tensors2):\n        assert torch.allclose(t1, t2)\n\n    # BW\n    grads = torch.unbind(torch.randn(x.shape), dim)\n    zero = torch.zeros_like(tensors[0])\n    loss1 = sum(((g * t) for (g, t) in zip(grads, tensors)), zero)\n    loss2 = sum(((g * t) for (g, t) in zip(grads, tensors2)), zero)\n    assert torch.allclose(loss1, loss2)\n    g = torch.randn_like(loss1)\n    loss1.backward(g)\n    loss2.backward(g)\n    assert x.grad is not None\n    assert x2.grad is not None\n    assert torch.allclose(x.grad, x2.grad)\n\n\n@pytest.mark.parametrize(\"contiguous\", [True, False])\n@pytest.mark.parametrize(\"dim\", [0, 1, 2, 3, 4])\ndef test_unbind_get_stack_strides(dim: int, contiguous: bool):\n    def not_stacked(t, d):\n        return xformers.ops.get_stack_strides(t, d) is None\n\n    x = torch.randn([10, 20, 4, 4, 3])\n    ndim = x.ndim\n\n    # Non-contiguous tensors\n    if not contiguous:\n        x = x.transpose(dim, (dim + 1) % ndim)\n    assert contiguous == x.is_contiguous()\n\n    tensors = xformers.ops.unbind(x, dim)\n    tensors2 = torch.unbind(x.clone(), dim)\n\n    for cat_dim in range(ndim):\n        permute = list(range(ndim))\n        permute.pop(dim)\n        permute.insert(cat_dim, dim)\n        x_permuted = x.permute(permute)\n        assert not_stacked([tensors2[0], tensors[1]], cat_dim), \"different storage\"\n        assert not_stacked(\n            [tensors[0], tensors[1].clone()], cat_dim\n        ), \"different storage\"\n\n        def test_slice(s):\n            slices = [slice(None) for _ in range(ndim)]\n            slices[cat_dim] = s\n            reference = x_permuted[tuple(slices)]\n            stacked = xformers.ops.stack_or_none(tensors[s], cat_dim)\n            assert stacked is not None\n            assert (\n                xformers.ops.get_stack_strides(tensors[s], cat_dim)\n                == reference.stride()\n            )\n            assert torch.allclose(stacked, torch.stack(tensors2[s], cat_dim))\n            assert _get_storage_base(stacked) == _get_storage_base(tensors[0])\n\n        # tensors\n        test_slice(slice(None))\n\n        # tensors[1:]\n        test_slice(slice(1, None))\n\n        # tensors[:2]\n        test_slice(slice(None, 2))\n\n        # tensors[::2]\n        test_slice(slice(None, None, 2))\n"
  },
  {
    "path": "tests/utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom functools import wraps\nfrom typing import Optional, Tuple\n\nimport numpy as np\nimport pytest\nimport torch\n\nfrom xformers.attn_bias_utils import pack_kv_cache, ref_attention, ref_attention_bmhk\nfrom xformers.ops.fmha import Inputs\nfrom xformers.ops.fmha.attn_bias import (\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n)\nfrom xformers.ops.fmha.triton_splitk import InputsFp8\n\ncuda_or_mtia_only = pytest.mark.skipif(\n    not torch.cuda.is_available() and not torch.mtia.is_available(),\n    reason=\"requires CUDA or MTIA\",\n)\ncuda_only = pytest.mark.skipif(not torch.cuda.is_available(), reason=\"requires CUDA\")\nrocm_only = pytest.mark.skipif(\n    not torch.cuda.is_available() or not torch.version.hip, reason=\"requires ROCM\"\n)\ndisable_on_rocm = pytest.mark.skipif(\n    not not torch.version.hip, reason=\"could not be done on ROCM\"\n)\ndisable_on_mtia = pytest.mark.skipif(\n    torch.mtia.is_available(), reason=\"Not supported yet on MTIA\"\n)\n\n\n# We don't want to compare MTIA output against another MTIA output yet for 2 reasons:\n#   1. Some kernels may have bugs, and the reference implementation may share some of\n#      the same kernels as the mem_eff implementation. We would then end up comparing\n#      a faulty output against another faulty output, which could lead to tests passing\n#      when they shouldn't.\n#   2. We may run on some emulated devices that can be slower than the CPU implementation,\n#      and therefore increase the time it takes to run the tests by a lot.\ndef use_cpu_ref(device: str):\n    return device.startswith(\"mtia\")\n\n\ndef maybe_use_cpu_ref(fn):\n    @wraps(fn)\n    def wrapped(*args, **kwargs):\n        new_args = list(args)\n        new_kwargs = kwargs.copy()\n        original_device = None\n\n        for key in kwargs:\n            if isinstance(kwargs[key], torch.Tensor) and use_cpu_ref(\n                kwargs[key].device.type\n            ):\n                assert original_device is None or kwargs[key].device == original_device\n                original_device = kwargs[key].device\n                new_kwargs[key] = kwargs[key].cpu()\n\n        for index, arg in enumerate(new_args):\n            if isinstance(arg, torch.Tensor) and use_cpu_ref(arg.device.type):\n                assert original_device is None or arg.device == original_device\n                original_device = arg.device\n                new_args[index] = arg.cpu()\n\n        output = fn(*new_args, **new_kwargs)\n\n        if isinstance(output, torch.Tensor) and original_device is not None:\n            output = output.to(original_device)\n\n        return output\n\n    return wrapped\n\n\ndef disable_tf32(fn):\n    @wraps(fn)\n    def wrapped(*args, **kwargs):\n        cuda, cudnn = (\n            torch.backends.cuda.matmul.allow_tf32,\n            torch.backends.cudnn.allow_tf32,\n        )\n        torch.backends.cuda.matmul.allow_tf32, torch.backends.cudnn.allow_tf32 = (\n            False,\n            False,\n        )\n        try:\n            return fn(*args, **kwargs)\n        finally:\n            torch.backends.cuda.matmul.allow_tf32, torch.backends.cudnn.allow_tf32 = (\n                cuda,\n                cudnn,\n            )\n\n    return wrapped\n\n\nref_attention_for_test = disable_tf32(maybe_use_cpu_ref(ref_attention))\nref_attention_bmhk_for_test = disable_tf32(maybe_use_cpu_ref(ref_attention_bmhk))\n\n\ndef assert_allclose(\n    out: Optional[torch.Tensor],\n    ref: Optional[torch.Tensor],\n    msg: str = \"failed\",\n    atol: float = 1e-8,\n    rtol: float = 1e-5,\n) -> None:\n    assert out is not None, f\"{msg}: output Tensor is None\"\n    assert ref is not None, f\"{msg}: reference Tensor is None\"\n    assert out.shape == ref.shape, f\"Shape: {out.shape} (expected: {ref.shape})\"\n    if out.dtype != ref.dtype:\n        assert False, f\"out dtype: {out.dtype}, ref dtype: {ref.dtype}\"\n    if out.numel() == 0:\n        return\n    flatten_diff = ((out - ref).abs() - atol - ref.abs() * rtol).flatten()\n    max_pos = flatten_diff.argmax()\n    max_location = np.unravel_index(int(max_pos), out.shape)\n    max_diff = flatten_diff[max_pos]\n    num_different = flatten_diff.numel() - torch.count_nonzero(flatten_diff <= 0)\n    percentage = num_different / flatten_diff.numel()\n    del flatten_diff\n    assert torch.allclose(out, ref, rtol=rtol, atol=atol), (\n        f\"{msg}: \"\n        f\"out={out.flatten()[max_pos]} and ref={ref.flatten()[max_pos]} (diff={max_diff} > 0)\"\n        f\" at {max_location} of shape {tuple(out.shape)} / atol={atol}, rtol={rtol}\"\n        f\"/ total failing elements: {num_different} ({percentage*100:.3}%)\"\n    )\n\n\ndef construct_fp8_attention_inputs(\n    B: int,\n    Mkv: int,\n    Mq: int,\n    Hkv: int,\n    Hq: int,\n    K: int,\n    page_size: int,\n    device: torch.device,\n    dtype: torch.dtype,\n    randomize_lengths: bool = True,\n) -> Tuple[InputsFp8, Inputs, InputsFp8, Inputs]:\n    \"\"\"\n    Construct inputs for benchmarks and tests of Triton Split-k attention\n    with fused row-wise FP8 dequantization.\n    Quantization coefficients are packed as int32 tensors where each\n    element contains two fp16 numbers - scales and shifts.\n    \"\"\"\n    G = Hq // Hkv\n    q = torch.randn(1, B * Mq, Hkv, G, K, dtype=dtype, device=device)\n    k = torch.randn(1, B * Mkv, Hkv, 1, K, dtype=dtype, device=device)\n    v = torch.randn(1, B * Mkv, Hkv, 1, K, dtype=dtype, device=device)\n\n    pt_fp8_dtype = (\n        torch.float8_e4m3fnuz if torch.version.hip is not None else torch.float8_e4m3fn\n    )\n\n    k_fp8, k_fp8_scales, k_fp8_shifts = quantize_fp8_asymmetric(\n        k.view(-1, K), pt_fp8_dtype=pt_fp8_dtype\n    )\n    v_fp8, v_fp8_scales, v_fp8_shifts = quantize_fp8_asymmetric(\n        v.view(-1, K), pt_fp8_dtype=pt_fp8_dtype\n    )\n\n    k_fp8, v_fp8 = k_fp8.view(torch.int32), v_fp8.view(torch.int32)\n\n    k_fp8_scales = k_fp8_scales.to(torch.float16)\n    v_fp8_scales = v_fp8_scales.to(torch.float16)\n    k_fp8_shifts = k_fp8_shifts.to(torch.float16)\n    v_fp8_shifts = v_fp8_shifts.to(torch.float16)\n\n    def _to_expanded_shape(x):\n        return x.view(1, B * Mkv, Hkv, 1, -1).expand(1, B * Mkv, Hkv, G, -1)\n\n    k_fp8 = _to_expanded_shape(k_fp8)\n    v_fp8 = _to_expanded_shape(v_fp8)\n\n    k_fp8_scales_shifts = _combine_scale_shift(k_fp8_scales, k_fp8_shifts)\n    v_fp8_scales_shifts = _combine_scale_shift(v_fp8_scales, v_fp8_shifts)\n\n    k_fp8_scales_shifts = (\n        _to_expanded_shape(k_fp8_scales_shifts).squeeze(-1).contiguous()\n    )\n    v_fp8_scales_shifts = (\n        _to_expanded_shape(v_fp8_scales_shifts).squeeze(-1).contiguous()\n    )\n\n    k_fp8_scales = _to_expanded_shape(k_fp8_scales).squeeze(-1).to(torch.float16)\n    v_fp8_scales = _to_expanded_shape(v_fp8_scales).squeeze(-1).to(torch.float16)\n    k_fp8_shifts = _to_expanded_shape(k_fp8_shifts).squeeze(-1).to(torch.float16)\n    v_fp8_shifts = _to_expanded_shape(v_fp8_shifts).squeeze(-1).to(torch.float16)\n\n    kv_seqlens = (\n        torch.randint(0, Mkv, size=(B,)).tolist() if randomize_lengths else [Mkv] * B\n    )\n\n    k_deq = dequantize_fp8_asymmetric(\n        k_fp8.view(pt_fp8_dtype), k_fp8_scales, k_fp8_shifts\n    ).to(dtype)\n    v_deq = dequantize_fp8_asymmetric(\n        v_fp8.view(pt_fp8_dtype), v_fp8_scales, v_fp8_shifts\n    ).to(dtype)\n\n    k_deq = k_deq[:, :, :, :1, :].contiguous()\n    v_deq = v_deq[:, :, :, :1, :].contiguous()\n\n    attn_bias = BlockDiagonalCausalWithOffsetPaddedKeysMask.from_seqlens(\n        q_seqlen=[1 for _ in range(B)],\n        kv_seqlen=kv_seqlens,\n        kv_padding=Mkv,\n    )\n    inp = InputsFp8(\n        query=q,\n        key=k_fp8,\n        value=v_fp8,\n        k_fp8_scale_shift=k_fp8_scales_shifts,\n        v_fp8_scale_shift=v_fp8_scales_shifts,\n        attn_bias=attn_bias,\n    )\n    inp_ref = Inputs(\n        query=q,\n        key=_to_expanded_shape(k_deq),\n        value=_to_expanded_shape(v_deq),\n        attn_bias=attn_bias,\n    )\n\n    # Paged K/V cache\n    block_tables, packed_cache_k, packed_cache_v = pack_kv_cache(\n        k_deq.view(B, Mkv, Hkv, -1),\n        v_deq.view(B, Mkv, Hkv, -1),\n        kv_seqlens,\n        page_size,\n    )\n    block_tables_orig, packed_cache_k_orig, packed_cache_v_orig = pack_kv_cache(\n        k.view(B, Mkv, Hkv, -1),\n        v.view(B, Mkv, Hkv, -1),\n        kv_seqlens,\n        page_size,\n    )\n    assert (block_tables_orig == block_tables).all()\n\n    (\n        packed_cache_k_fp8,\n        packed_k_fp8_scales,\n        packed_k_fp8_shifts,\n    ) = quantize_fp8_asymmetric(\n        packed_cache_k_orig.view(-1, K), pt_fp8_dtype=pt_fp8_dtype\n    )\n    (\n        packed_cache_v_fp8,\n        packed_v_fp8_scales,\n        packed_v_fp8_shifts,\n    ) = quantize_fp8_asymmetric(\n        packed_cache_v_orig.view(-1, K), pt_fp8_dtype=pt_fp8_dtype\n    )\n\n    total_len_rounded = packed_cache_k_fp8.shape[0] // Hkv\n\n    packed_cache_k_fp8 = packed_cache_k_fp8.view(torch.int32).view(\n        1, total_len_rounded, Hkv, -1\n    )\n    packed_cache_v_fp8 = packed_cache_v_fp8.view(torch.int32).view(\n        1, total_len_rounded, Hkv, -1\n    )\n\n    packed_k_fp8_scales_shifts = _combine_scale_shift(\n        packed_k_fp8_scales, packed_k_fp8_shifts\n    )\n    packed_v_fp8_scales_shifts = _combine_scale_shift(\n        packed_v_fp8_scales, packed_v_fp8_shifts\n    )\n\n    def _to_packed_expanded_shape(x):\n        return x.reshape(1, total_len_rounded, Hkv, 1, -1).expand(\n            1, total_len_rounded, Hkv, Hq // Hkv, -1\n        )\n\n    packed_k_fp8_scales_shifts = (\n        _to_packed_expanded_shape(packed_k_fp8_scales_shifts).squeeze(-1).contiguous()\n    )\n    packed_v_fp8_scales_shifts = (\n        _to_packed_expanded_shape(packed_v_fp8_scales_shifts).squeeze(-1).contiguous()\n    )\n\n    attn_bias_paged = attn_bias.make_paged(\n        block_tables=block_tables,\n        page_size=page_size,\n        paged_type=PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    )\n    inp_bf16_paged = Inputs(\n        query=q,\n        key=_to_packed_expanded_shape(packed_cache_k),\n        value=_to_packed_expanded_shape(packed_cache_v),\n        attn_bias=attn_bias_paged,\n    )\n    inp_fp8_paged = InputsFp8(\n        query=q,\n        key=_to_packed_expanded_shape(packed_cache_k_fp8),\n        value=_to_packed_expanded_shape(packed_cache_v_fp8),\n        attn_bias=attn_bias_paged,\n        k_fp8_scale_shift=packed_k_fp8_scales_shifts,\n        v_fp8_scale_shift=packed_v_fp8_scales_shifts,\n    )\n\n    return inp, inp_ref, inp_fp8_paged, inp_bf16_paged\n\n\ndef _combine_scale_shift(scale: torch.Tensor, shift: torch.Tensor) -> torch.Tensor:\n    return (\n        torch.concat([scale.unsqueeze(-1), shift.unsqueeze(-1)], dim=-1)\n        .flatten(-2)\n        .to(torch.float16)\n        .view(torch.int32)\n    )\n\n\ndef quantize_fp8_asymmetric(\n    x: torch.Tensor,\n    pt_fp8_dtype: torch.dtype = torch.float8_e4m3fn,\n) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n    max_fp8 = torch.finfo(pt_fp8_dtype).max\n\n    shift = x.mean(dim=1)\n    x_centered = x - shift[..., None]\n\n    row_max: torch.Tensor = x_centered.abs().max(dim=-1)[0]\n    scale = max_fp8 * row_max.to(torch.float32).pow(-1)\n    scale = torch.nan_to_num(scale, posinf=1)\n\n    x_quant = (x_centered / scale[..., None]).to(pt_fp8_dtype)\n    return x_quant, scale, shift\n\n\ndef dequantize_fp8_asymmetric(\n    x: torch.Tensor, scale: torch.Tensor, shift: torch.Tensor\n) -> torch.Tensor:\n    return x.to(scale.dtype) * scale[..., None] + shift[..., None]\n"
  },
  {
    "path": "version.txt",
    "content": "0.0.35\n"
  },
  {
    "path": "xformers/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport logging\nimport os\n\nimport torch\n\nfrom . import _cpp_lib\nfrom .checkpoint import (  # noqa: E402, F401\n    checkpoint,\n    get_optimal_checkpoint_policy,\n    list_operators,\n    selective_checkpoint_wrapper,\n)\n\ntry:\n    from .version import __version__  # noqa: F401\nexcept ImportError:\n    __version__ = \"0.0.0\"\n\n\nlogger = logging.getLogger(\"xformers\")\n\n_has_cpp_library: bool = _cpp_lib._cpp_library_load_exception is None\n\n_is_opensource: bool = True\n\n\ndef compute_once(func):\n    value = None\n\n    def func_wrapper():\n        nonlocal value\n        if value is None:\n            value = func()\n        return value\n\n    return func_wrapper\n\n\n@compute_once\ndef _is_triton_available():\n    if os.environ.get(\"XFORMERS_ENABLE_TRITON\", \"0\") == \"1\":\n        return True\n    if not torch.cuda.is_available():\n        return False\n    if os.environ.get(\"XFORMERS_FORCE_DISABLE_TRITON\", \"0\") == \"1\":\n        return False\n    # We have many errors on V100 with recent triton versions\n    # Let's just drop support for triton kernels below A100\n    if torch.cuda.get_device_capability(\"cuda\") < (8, 0):\n        return False\n    try:\n        import triton  # noqa\n\n        return True\n    except (ImportError, AttributeError):\n        logger.warning(\n            \"A matching Triton is not available, some optimizations will not be enabled\",\n            exc_info=True,\n        )\n        return False\n\n\n@compute_once\ndef get_python_lib():\n    return torch.library.Library(\"xformers_python\", \"DEF\")\n\n\n# end of file\n"
  },
  {
    "path": "xformers/_cpp_lib.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport dataclasses\nimport json\nimport logging\nimport os\nimport platform\nfrom typing import Any, Dict, Optional\n\nimport torch\n\nlogger = logging.getLogger(\"xformers\")\n\nUNAVAILABLE_FEATURES_MSG = (\n    \"  Memory-efficient attention, SwiGLU, sparse and more won't be available.\"\n)\n\n\n@dataclasses.dataclass\nclass _BuildInfo:\n    metadata: Dict[str, Any]\n\n    @property\n    def cuda_version(self) -> Optional[int]:\n        return self.metadata[\"version\"][\"cuda\"]\n\n    @property\n    def hip_version(self) -> Optional[int]:\n        return self.metadata[\"version\"][\"hip\"]\n\n    @property\n    def torch_version(self) -> str:\n        return self.metadata[\"version\"][\"torch\"]\n\n    @property\n    def python_version(self) -> str:\n        return self.metadata[\"version\"][\"python\"]\n\n    @property\n    def flash_version(self) -> str:\n        return self.metadata[\"version\"].get(\"flash\", \"0.0.0\")\n\n    @property\n    def use_torch_flash(self) -> bool:\n        return self.metadata[\"version\"].get(\"use_torch_flash\", False)\n\n    @property\n    def build_env(self) -> Dict[str, Any]:\n        return self.metadata[\"env\"]\n\n\nclass xFormersWasNotBuiltException(Exception):\n    def __str__(self) -> str:\n        return (\n            \"Need to compile C++ extensions to use all xFormers features.\\n\"\n            \"    Please install xformers properly \"\n            \"(see https://github.com/facebookresearch/xformers#installing-xformers)\\n\"\n            + UNAVAILABLE_FEATURES_MSG\n        )\n\n\nclass xFormersInvalidLibException(Exception):\n    def __init__(self, build_info: Optional[_BuildInfo]) -> None:\n        self.build_info = build_info\n\n    def __str__(self) -> str:\n        if self.build_info is None:\n            msg = \"xFormers was built for a different version of PyTorch or Python.\"\n        else:\n            msg = f\"\"\"xFormers was built for:\n    PyTorch {self.build_info.torch_version} with CUDA {self.build_info.cuda_version} (you have {torch.__version__})\n    Python  {self.build_info.python_version} (you have {platform.python_version()})\"\"\"\n        return (\n            \"xFormers can't load C++/CUDA extensions. \"\n            + msg\n            + \"\\n  Please reinstall xformers \"\n            \"(see https://github.com/facebookresearch/xformers#installing-xformers)\\n\"\n            + UNAVAILABLE_FEATURES_MSG\n        )\n\n\ndef _register_extensions():\n    import importlib\n    import os\n\n    import torch\n\n    # load the custom_op_library and register the custom ops\n    lib_dir = os.path.dirname(__file__)\n    if os.name == \"nt\":\n        # Register the main torchvision library location on the default DLL path\n        import ctypes\n        import sys\n\n        kernel32 = ctypes.WinDLL(\"kernel32.dll\", use_last_error=True)\n        with_load_library_flags = hasattr(kernel32, \"AddDllDirectory\")\n        prev_error_mode = kernel32.SetErrorMode(0x0001)\n\n        if with_load_library_flags:\n            kernel32.AddDllDirectory.restype = ctypes.c_void_p\n\n        if sys.version_info >= (3, 8):\n            os.add_dll_directory(lib_dir)\n        elif with_load_library_flags:\n            res = kernel32.AddDllDirectory(lib_dir)\n            if res is None:\n                err = ctypes.WinError(ctypes.get_last_error())\n                err.strerror += f' Error adding \"{lib_dir}\" to the DLL directories.'\n                raise err\n\n        kernel32.SetErrorMode(prev_error_mode)\n\n    loader_details = (\n        importlib.machinery.ExtensionFileLoader,\n        importlib.machinery.EXTENSION_SUFFIXES,\n    )\n\n    extfinder = importlib.machinery.FileFinder(lib_dir, loader_details)\n    if torch.version.hip and not hasattr(torch.version, \"git_version\"):\n        ext_specs = extfinder.find_spec(\"_C_hip\")\n    else:\n        ext_specs = extfinder.find_spec(\"_C\")\n    if ext_specs is None:\n        raise xFormersWasNotBuiltException()\n    cpp_lib_json = os.path.join(lib_dir, \"cpp_lib.json\")\n    with open(cpp_lib_json, \"r\") as fp:\n        build_metadata = _BuildInfo(json.load(fp))\n    try:\n        torch.ops.load_library(ext_specs.origin)\n    except OSError as exc:\n        raise xFormersInvalidLibException(build_metadata) from exc\n    return build_metadata\n\n\n_cpp_library_load_exception = None\n_build_metadata: Optional[_BuildInfo] = None\n\ntry:\n    _build_metadata = _register_extensions()\nexcept (xFormersInvalidLibException, xFormersWasNotBuiltException) as e:\n    ENV_VAR_FOR_DETAILS = \"XFORMERS_MORE_DETAILS\"\n    if os.environ.get(ENV_VAR_FOR_DETAILS, False):\n        logger.warning(f\"WARNING[XFORMERS]: {e}\", exc_info=e)\n    else:\n        logger.warning(\n            f\"WARNING[XFORMERS]: {e}\\n  Set {ENV_VAR_FOR_DETAILS}=1 for more details\"\n        )\n    _cpp_library_load_exception = e\n\n_built_with_cuda = (\n    _build_metadata is not None and _build_metadata.cuda_version is not None\n)\n"
  },
  {
    "path": "xformers/_deprecation_warning.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport warnings\n\n\ndef deprecated_function(self):\n    name = repr(self)  # self.__name__\n    msg = f\"{name} is deprecated and is not maintained anymore. It might be removed in a future version of xFormers\"\n    warnings.warn(msg, FutureWarning, stacklevel=2)\n"
  },
  {
    "path": "xformers/attn_bias_utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nimport random\nfrom typing import List, Optional, Sequence, Tuple, Type\n\nimport torch\n\nfrom xformers.ops import AttentionBias, fmha\nfrom xformers.ops.fmha.common import AttentionOpBase\n\n\ndef _create_aligned_bias(*shape: int, **kwargs) -> torch.Tensor:\n    align_to = 8\n    return (\n        torch.randn(\n            (\n                *shape[:-1],\n                align_to * ((shape[-1] + align_to - 1) // align_to),\n            ),\n            **kwargs,\n        )\n        * 3\n    ).narrow(-1, 0, shape[-1])\n\n\ndef create_attn_bias(\n    bias_type,\n    batch_size: int,\n    num_heads: int,\n    num_heads_groups: int,\n    q_len: int,\n    kv_len: int,\n    device,\n    dtype,\n    requires_grad: bool,\n    fmt: str,\n    op: Optional[Type[AttentionOpBase]] = None,\n    page_size: Optional[int] = None,\n):\n    if bias_type is None or isinstance(None, bias_type):\n        return None\n    r = random.Random(\"-\".join(map(str, [batch_size, q_len, kv_len, dtype, fmt])))\n    window_size = {0: 3, 1: 128, 2: 300}[r.randint(0, 2)]\n    if bias_type is torch.Tensor:\n        if fmt == \"BMK\":\n            batch_size *= num_heads\n            num_heads = 1\n        if op is not None and issubclass(op, fmha.triton_splitk.FwOp):\n            attn_bias = (\n                torch.randn(\n                    (batch_size, num_heads_groups, num_heads, q_len, kv_len),\n                    device=device,\n                    dtype=dtype,\n                )\n                * 3\n            )\n            if fmt in [\"BMK\", \"BMHK\"]:\n                attn_bias = attn_bias[:, 0]\n        else:\n            attn_bias = _create_aligned_bias(\n                batch_size,\n                num_heads_groups,\n                num_heads,\n                q_len,\n                kv_len,\n                device=device,\n                dtype=dtype,\n            )\n\n            # make sure it also works if the first columns/rows are partially masked out\n            attn_bias[0, 0, 0, : q_len - 1, : kv_len - 1] = -math.inf\n            if fmt in [\"BMK\", \"BMHK\"]:\n                attn_bias = attn_bias[:, 0]\n\n        if requires_grad:\n            attn_bias.requires_grad_(True)\n        if fmt == \"BMK\":\n            attn_bias = attn_bias[:, 0]\n        return attn_bias\n    if bias_type is fmha.attn_bias.LowerTriangularMask:\n        return bias_type()\n    if bias_type is fmha.attn_bias.LowerTriangularFromBottomRightMask:\n        return bias_type()\n    if bias_type is fmha.attn_bias.LowerTriangularFromBottomRightLocalAttentionMask:\n        return bias_type(window_size)\n    if bias_type is fmha.attn_bias.LowerTriangularMaskWithTensorBias:\n        attn_bias = _create_aligned_bias(\n            batch_size,\n            num_heads_groups,\n            num_heads,\n            q_len,\n            kv_len,\n            device=device,\n            dtype=dtype,\n        )\n        if fmt in [\"BMK\", \"BMHK\"]:\n            attn_bias = attn_bias[:, 0]\n        if fmt == \"BMK\":\n            attn_bias = attn_bias[:, 0]\n        if requires_grad:\n            attn_bias.requires_grad_(True)\n        return fmha.attn_bias.LowerTriangularMaskWithTensorBias(attn_bias)\n    if bias_type in [\n        fmha.attn_bias.BlockDiagonalMask,\n        fmha.attn_bias.BlockDiagonalCausalMask,\n        fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask,\n        fmha.attn_bias.BlockDiagonalCausalLocalAttentionMask,\n        fmha.attn_bias.BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    ]:\n        # These bias types are not supported in BMK format\n        assert fmt in [\"BMGHK\", \"BMHK\"]\n        max_q_minus_k = None\n        if bias_type in {\n            fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask,\n            fmha.attn_bias.BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        }:\n            max_q_minus_k = 0\n        elif bias_type == fmha.attn_bias.BlockDiagonalCausalLocalAttentionMask:\n            assert window_size is not None\n            max_q_minus_k = window_size - 1\n\n        block_diag = fmha.attn_bias.BlockDiagonalMask.from_seqlens(\n            *_rand_seqlens(\n                r,\n                batch_size,\n                q_len,\n                kv_len,\n                max_q_minus_k=max_q_minus_k,\n            )\n        )\n        if bias_type is fmha.attn_bias.BlockDiagonalCausalMask:\n            block_diag = block_diag.make_causal()\n        if bias_type in {\n            fmha.attn_bias.BlockDiagonalCausalLocalAttentionMask,\n            fmha.attn_bias.BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        }:\n            block_diag = fmha.attn_bias.BlockDiagonalMask(\n                q_seqinfo=block_diag.q_seqinfo,\n                k_seqinfo=block_diag.k_seqinfo,\n                _batch_sizes=block_diag._batch_sizes,\n            )\n            assert window_size is not None\n            if bias_type is fmha.attn_bias.BlockDiagonalCausalLocalAttentionMask:\n                block_diag = block_diag.make_local_attention(window_size)\n            else:\n                block_diag = block_diag.make_local_attention_from_bottomright(\n                    window_size\n                )\n        if bias_type is fmha.attn_bias.BlockDiagonalCausalFromBottomRightMask:\n            block_diag = block_diag.make_causal_from_bottomright()\n        return block_diag\n    if bias_type in [\n        fmha.attn_bias.BlockDiagonalPaddedKeysMask,\n        fmha.attn_bias.BlockDiagonalLocalAttentionPaddedKeysMask,\n        fmha.attn_bias.BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask,\n        fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    ]:\n        assert fmt in [\"BMHK\", \"BMGHK\"]\n        q, k = _rand_seqlens_padded_k(r, batch_size, q_len, kv_len)\n        block_diag_type = (\n            bias_type._UNPAGED_TYPE\n            if issubclass(bias_type, fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask)\n            else bias_type\n        )\n        if bias_type is fmha.attn_bias.BlockDiagonalCausalLocalAttentionPaddedKeysMask:\n            g_block_diag = block_diag_type.from_seqlens_local(\n                q_seqlen=q,\n                kv_padding=kv_len,\n                kv_seqlen=k,\n                window_size=min(window_size, min(k)),\n            )\n        elif bias_type is fmha.attn_bias.BlockDiagonalLocalAttentionPaddedKeysMask:\n            g_block_diag = block_diag_type.from_seqlens_local(\n                q_seqlen=q,\n                kv_padding=kv_len,\n                kv_seqlen=k,\n                window_left=max(window_size, max(q)) + 1,\n                window_right=max(window_size, max(q)) + 1,\n            )\n        else:\n            g_block_diag = block_diag_type.from_seqlens(\n                q_seqlen=q,\n                kv_padding=kv_len,\n                kv_seqlen=k,\n            )\n        if issubclass(bias_type, fmha.attn_bias.PagedBlockDiagonalPaddedKeysMask):\n            assert page_size is not None\n            pages_per_row = (kv_len + page_size - 1) // page_size\n            block_tables = torch.tensor(\n                r.sample(range(batch_size * pages_per_row), batch_size * pages_per_row),\n                device=device,\n                dtype=torch.int32,\n            ).reshape(batch_size, pages_per_row)\n            return g_block_diag.make_paged(\n                block_tables=block_tables, page_size=page_size, paged_type=bias_type\n            )\n        return g_block_diag\n    if bias_type in [\n        fmha.attn_bias.BlockDiagonalCausalWithOffsetGappyKeysMask,\n        fmha.attn_bias.BlockDiagonalGappyKeysMask,\n    ]:\n        assert fmt in [\"BMHK\", \"BMGHK\"]\n        max_q_minus_k = (\n            None if bias_type is fmha.attn_bias.BlockDiagonalGappyKeysMask else 0\n        )\n        q, k = _rand_seqlens(r, batch_size, q_len, kv_len, max_q_minus_k)\n        total_kv_len = kv_len * batch_size\n        starts = [r.randint(0, total_kv_len - ki) for ki in k] + [total_kv_len]\n        return fmha.attn_bias.BlockDiagonalGappyKeysMask.from_seqlens(\n            q_seqlen=q,\n            kv_seqstarts=starts,\n            kv_seqlen=k,\n        )\n    if issubclass(bias_type, fmha.attn_bias.PagedBlockDiagonalGappyKeysMask):\n        assert fmt in [\"BMHK\", \"BMGHK\"]\n        assert page_size is not None\n        pages_per_row = (kv_len + page_size - 1) // page_size\n        total_queries = q_len * batch_size\n        if issubclass(\n            bias_type, fmha.attn_bias.PagedBlockDiagonalCausalWithOffsetGappyKeysMask\n        ):\n            q, k = _rand_seqlens_padded_k(r, batch_size, q_len, kv_len)\n        else:\n            q = _rand_maxed_partition(\n                r, total_queries, batch_size, total_queries, False\n            )\n            k = [r.randint(1, kv_len) for _ in range(batch_size)]\n        row_size = pages_per_row * page_size\n        starts = [row_size * i + r.randint(0, row_size - ki) for i, ki in enumerate(k)]\n        starts.append(pages_per_row * batch_size * page_size)\n        block_diag_type = bias_type._UNPAGED_TYPE  # type: ignore\n        g_block_diag = block_diag_type.from_seqlens(\n            q_seqlen=q,\n            kv_seqstarts=starts,\n            kv_seqlen=k,\n        )\n        block_tables = torch.tensor(\n            r.sample(range(batch_size * pages_per_row), batch_size * pages_per_row),\n            device=device,\n            dtype=torch.int32,\n        ).reshape(batch_size, pages_per_row)\n        return g_block_diag.make_paged(\n            block_tables=block_tables,\n            page_size=page_size,\n            paged_type=bias_type,\n            notional_padding=page_size * pages_per_row,\n        )\n    if bias_type == fmha.attn_bias.LocalAttentionFromBottomRightMask:\n        return bias_type(\n            window_left=r.randint(0, 5),\n            window_right=r.randint(0, 5),\n        )\n\n    assert False, f\"Unsupported bias type: {bias_type}\"\n\n\ndef _rand_seqlens(\n    r: random.Random,\n    bs: int,\n    q_len: int,\n    kv_len: int,\n    max_q_minus_k: Optional[int],\n) -> Tuple[Sequence[int], Sequence[int]]:\n    \"\"\"\n    Generates lists of lengths of query blocks and corresponding key blocks.\n    The total number of queries will be bs * q_len and the\n    total number of keys will be bs * kv_len.\n    max_q_minus_k: maximum allowed num_queries - num_keys.\n        For \"bottom-right\" masks it's 0, we need to have more keys than\n        queries, otherwise some queries have no keys to attend to.\n        For BlockDiagonalCausalMask it's None, there is no constraint\n        on num_queries - num_keys.\n        For BlockDiagonalCausalLocalAttentionMask it's equal\n        to the window size.\n    \"\"\"\n    if max_q_minus_k == 0:\n        # In case max_q_minus_k > 0 the exact condition is\n        # kv_len >= q_len - max_q_minus_k * batch_size,\n        # but we can't check it without knowing the actual batch size,\n        # which is determined in the loop below.\n        assert kv_len >= q_len\n    q_len *= bs\n    kv_len *= bs\n    seqlens_q: List[int] = []\n    seqlens_k: List[int] = []\n\n    step_q = [max(1, q_len // 10), max(2, q_len // 2)]\n    step_k = [max(1, kv_len // 10), max(2, kv_len // 2)]\n    while sum(seqlens_q) < q_len and sum(seqlens_k) < kv_len:\n        if max_q_minus_k is None:\n            # Simple case - no constraint on the number of queries and keys.\n            num_queries = r.randrange(*step_q)\n            seqlens_q.append(num_queries)\n            seqlens_k.append(r.randrange(*step_k))\n        else:\n            # In this case we need to make sure num_queries - num_keys < max_q_minus_k holds for every batch element.\n            # To do this, when choosing num_queries and num_keys at a given step,\n            # we ensure two conditions are satisfied:\n            # 1) num_queries <= num_keys + max_q_minus_k for the current batch element\n            # 2) Same holds for the remaining keys and queries, i.e.\n            #    queries_left - num_queries <= keys_left - num_keys + max_q_minus_k\n            keys_left = kv_len - sum(seqlens_k, 0)\n            queries_left = q_len - sum(seqlens_q, 0)\n\n            assert (\n                keys_left >= queries_left - max_q_minus_k\n            ), f\"{keys_left=} {queries_left=} {max_q_minus_k=} {kv_len=} {q_len=} {seqlens_k=} {seqlens_q=}\"\n            # Limit num_queries from above: if num_queries > keys_left + max_q_minus_k,\n            # condition num_queries <= num_keys + max_q_minus_k can't be satisfied even if we take\n            # all the remaining keys\n            max_queries_to_take = min(queries_left, keys_left + max_q_minus_k)\n            num_queries = r.randrange(1, max_queries_to_take + 1)\n            seqlens_q.append(num_queries)\n\n            # Now we know num_queries, let's select num_keys.\n            # How many keys can we use for the current batch element so that\n            # for the remaining keys and values the constraint\n            # num_queries - num_keys < max_q_minus_k holds on the next step?\n            extra_keys_available = keys_left - queries_left + max_q_minus_k + 1\n            assert extra_keys_available >= 0\n            if extra_keys_available > 0:\n                seqlens_k.append(num_queries + r.randrange(0, extra_keys_available))\n            else:\n                seqlens_k.append(num_queries)\n    seqlens_q[-1] = q_len - sum(seqlens_q[:-1])\n    seqlens_k[-1] = kv_len - sum(seqlens_k[:-1])\n    return seqlens_q, seqlens_k\n\n\ndef _rand_maxed_partition(\n    r: random.Random, total: int, n: int, mx: int, positive: bool = True\n) -> List[int]:\n    # returns list of n nonnegative integers less than mx summing to total\n    # NB: This is unfortunately biased towards evenly-split bins.\n    # If `positive`, outputs are positive\n    if positive:\n        total -= n\n        mx -= 1\n    idxs = r.sample(range(n * mx), total)\n    y = torch.zeros(n, mx, dtype=torch.int32)\n    y.flatten()[idxs] = 1\n    z = y.sum(1)\n    if positive:\n        z += 1\n    return z.tolist()\n\n\ndef _rand_seqlens_padded_k(\n    r: random.Random, bs: int, q_len: int, kv_len: int\n) -> Tuple[Sequence[int], Sequence[int]]:\n    # This is for BlockDiagonalCausalWithOffsetPaddedKeysMask.\n    # we need q_seqlens and k_seqlens to be of len bsz.\n    # For each \"batch element\" there must be more keys than queries\n    # because this bias type is \"bottom right\" and so any extra queries\n    # will attend to nothing and have undefined result.\n    # In addition every element of k_seqlens must be <= kv_len\n    if q_len > kv_len:\n        raise ValueError(\"need more queries than keys\")\n    if q_len == kv_len:\n        # all key slots are needed so we cannot have padding\n        q_seqlens = k_seqlens = [kv_len] * bs\n    else:\n        q_seqlens = _rand_maxed_partition(r, q_len * bs, bs, kv_len)\n        k_seqlens = [r.randint(i, kv_len) for i in q_seqlens]\n    return q_seqlens, k_seqlens\n\n\ndef ref_attention(q, k, v, attn_bias=None, drop_mask=None, p=0.0, scale=None):\n    if q.ndim == 5:\n\n        def attn_bias_group(group: int):\n            if isinstance(attn_bias, torch.Tensor):\n                return attn_bias[:, group]\n            if isinstance(attn_bias, fmha.attn_bias.LowerTriangularMaskWithTensorBias):\n                return fmha.attn_bias.LowerTriangularMaskWithTensorBias(\n                    attn_bias._bias[:, group]\n                )\n            return attn_bias\n\n        return torch.stack(\n            [\n                ref_attention_bmhk(\n                    q[:, :, g],\n                    k[:, :, g],\n                    v[:, :, g],\n                    scale=scale,\n                    attn_bias=attn_bias_group(g),\n                )\n                for g in range(q.shape[2])\n            ],\n            dim=2,\n        )\n    if q.ndim == 4:\n        assert p == 0.0\n        return ref_attention_bmhk(q, k, v, scale=scale, attn_bias=attn_bias)\n    q = q.float()\n    k = k.float()\n    v = v.float()\n\n    scale = scale if scale is not None else (1 / q.shape[-1] ** 0.5)\n    q = q * scale\n\n    attn = q @ k.transpose(-2, -1)\n    if attn_bias is not None:\n        if isinstance(attn_bias, AttentionBias):\n            # Always create in B,H,Mq,Mk format\n            attn_bias_tensor = attn_bias.materialize(\n                (q.shape[0], 1, q.shape[1], k.shape[1]),\n                device=q.device,\n                dtype=torch.float32,\n            )\n        else:\n            attn_bias_tensor = attn_bias\n        if attn_bias_tensor.ndim == 4:\n            assert q.shape[0] == attn_bias_tensor.shape[0] * attn_bias_tensor.shape[1]\n            attn_bias_tensor = attn_bias_tensor.reshape(\n                [-1, *attn_bias_tensor.shape[2:]]\n            )\n        attn = attn + attn_bias_tensor.float()\n    attn = attn.softmax(-1)\n    if drop_mask is not None:\n        attn = attn * (drop_mask / (1 - p))\n    return attn @ v\n\n\ndef ref_attention_bmhk(q, k, v, attn_bias, scale=None) -> torch.Tensor:\n    assert q.ndim == 4\n\n    def T(t):\n        return t.permute((0, 2, 1, 3)).reshape(\n            [t.shape[0] * t.shape[2], t.shape[1], t.shape[3]]\n        )\n\n    if isinstance(attn_bias, AttentionBias):\n        attn_bias = attn_bias.materialize(\n            (q.shape[0], q.shape[2], q.shape[1], k.shape[1]),\n            device=q.device,\n            dtype=torch.float32,\n        ).reshape([q.shape[0] * q.shape[2], q.shape[1], k.shape[1]])\n    out = ref_attention(T(q), T(k), T(v), attn_bias, scale=scale)\n    out = out.reshape([q.shape[0], q.shape[2], q.shape[1], v.shape[3]])\n    return out.permute((0, 2, 1, 3))\n\n\ndef pack_kv_cache(\n    cache_k: torch.Tensor,\n    cache_v: torch.Tensor,\n    kv_seqlens: List[int],\n    BLOCK_N: int,\n) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n    \"\"\"\n    Create block tables and pages K/V cache for testing paged attention.\n    Args:\n        cache_k, cache_v: K/V caches, each of shape [B, MAX_T, H_kv, D].\n            Note that these tensors are unexpanded,\n            i.e. for multiquery case cache_k.shape[2] = 1\n        kv_seqlens: list of K/V sequence lengths\n        BLOCK_N: number of tokens per per paged attention block\n        B: batch size\n    Returns:\n        block_tables: [B, MAX_BLOCKS]\n        packed_cache_k: [1, total_len_rounded, H_kv, D]\n        packed_cache_v: [1, total_len_rounded, H_kv, D]\n    where total_len_rounded is a sum of K/V seqlens, each rounded up\n    to a multiple of BLOCK_N.\n    \"\"\"\n\n    kv_seqlens_rounded = [(x + BLOCK_N - 1) // BLOCK_N * BLOCK_N for x in kv_seqlens]\n\n    total_len_rounded = sum(kv_seqlens_rounded)\n\n    B, MAX_T, H, D = cache_k.shape\n\n    packed_cache_k = torch.empty(\n        total_len_rounded, H, D, device=cache_k.device, dtype=cache_k.dtype\n    )\n    packed_cache_v = torch.empty(\n        total_len_rounded, H, D, device=cache_k.device, dtype=cache_k.dtype\n    )\n    seqstart = 0\n    for b in range(B):\n        packed_cache_k[seqstart : seqstart + kv_seqlens[b]] = cache_k[\n            b, : kv_seqlens[b]\n        ].clone()\n        packed_cache_v[seqstart : seqstart + kv_seqlens[b]] = cache_v[\n            b, : kv_seqlens[b]\n        ].clone()\n        seqstart += kv_seqlens_rounded[b]\n\n    num_blocks_per_row = (MAX_T + BLOCK_N - 1) // BLOCK_N\n    block_tables = (\n        torch.arange(num_blocks_per_row, device=\"cuda\", dtype=torch.int32)\n        .unsqueeze(0)\n        .expand(B, num_blocks_per_row)\n    )\n    seqstarts = (\n        (\n            torch.tensor(kv_seqlens_rounded).cumsum(dim=0)\n            - torch.tensor(kv_seqlens_rounded)\n        )\n        .to(device=\"cuda\")\n        .unsqueeze(1)\n    ) // BLOCK_N\n    block_tables = (block_tables + seqstarts).contiguous().to(dtype=torch.int32)\n    return (\n        block_tables,\n        packed_cache_k.unsqueeze(0),\n        packed_cache_v.unsqueeze(0),\n    )\n"
  },
  {
    "path": "xformers/benchmarks/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_attn_decoding.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n# Run with --omit-baselines to skip slow baselines.\n# See other CLI arguments in benchmark_main_helper in utils.py.\n\nimport sys\nfrom typing import Any, Dict, Type\n\nimport pytest\nimport torch\n\nimport xformers.ops as xops\nfrom xformers.attn_bias_utils import create_attn_bias\nfrom xformers.benchmarks.utils import benchmark_main_helper2, NotSupportedInputError\n\nmin_run_time = 0.5\ndevice = torch.device(\"cuda\")\n\n\nCASES = [\n    dict(\n        B=max(1, 2 ** (16 - i)),\n        Mq=1,\n        Mkv=2**i,\n        Hq=16,\n        Hkv=hkv,\n        K=128,\n        attn_bias_type=xops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    )\n    for i in range(8, 18)\n    for hkv in (1, 2)\n]\n\n\ndef quantize_kv_int4(k: torch.Tensor, num_groups: int = 1) -> torch.Tensor:\n    \"\"\"\n    Auxiliary int4 row quantization function used for benchmarking and tests.\n    Matches the behaviour of torch.ops.llama_cpp.dequantize_int4_cache -\n    quantization parameters (scale and offset) of each row along the last\n    dimension of the tensor are assumed to be packed into two float16 values\n    at the beginning of the row.\n    \"\"\"\n    # Scale and shift are such that quantization linearly maps int4 values range [0..15]\n    # to input values range min(k)..max(k) individually for every row\n    k = k.reshape(*k.shape[:-1], num_groups, k.shape[-1] // num_groups)\n    # print(f\"k_reshape = {k.shape}\")\n    max_vals = torch.max(k, dim=-1, keepdim=True).values\n    min_vals = torch.min(k, dim=-1, keepdim=True).values\n    scale_k: torch.Tensor = (max_vals - min_vals) / 15\n    # print(f\"scale_k_shape = {scale_k.shape}\")\n\n    shift_k = torch.min(k, dim=-1, keepdim=True).values\n    scale_k = scale_k.to(torch.float16)\n    shift_k = shift_k.to(torch.float16)\n    in_bytes = ((k - shift_k.expand(k.shape)) / scale_k.expand(k.shape)) + 0.5\n    in_bytes = in_bytes.to(torch.uint8)\n    in_int4 = in_bytes & 0xF\n    in_int4_packed = in_int4[..., ::2] + (in_int4[..., 1::2] << 4)\n    scale_shift = torch.concat(\n        [scale_k.view(torch.uint8), shift_k.view(torch.uint8)], dim=-1\n    )\n    k_quant = torch.concat(\n        [\n            scale_shift.flatten(start_dim=-2),\n            in_int4_packed.flatten(start_dim=-2),\n        ],\n        dim=-1,\n    ).view(torch.int16)\n    return k_quant\n\n\nclass AttentionDecodingBase:\n    OP: Any = None\n\n    def __init__(\n        self,\n        B: int,\n        Mq: int,\n        Mkv: int,\n        Hq: int,\n        Hkv: int,\n        K: int,\n        bw: bool,\n        attn_bias_type,\n    ) -> None:\n        dtype = torch.float16\n        torch.manual_seed(10)\n        self.sub_label = (\n            f\"B={B} Mq={Mq} Mkv={Mkv} Hq={Hq} Hkv={Hkv} K={K} TotalBytes=\"\n            f\"{((B * Mkv * Hkv * K * 2) + (B * Mq * Hq * K) + (B * Mq * Hq * K)) * 2}\"\n        )\n        self.label = \"attn_decoding\"\n        self.shapes = (B, Mq, Mkv, Hq, Hkv, K)\n\n        assert Hkv <= Hq\n        assert Hq % Hkv == 0\n        self.q = torch.randn(\n            [B, Mq, Hkv, Hq // Hkv, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.k = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n        self.v = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n\n        if Hq == Hkv:\n            self.q = self.q[:, :, :, 0]\n            self.k = self.k[:, :, :, 0]\n            self.v = self.v[:, :, :, 0]\n        if Hkv == 1:\n            self.q = self.q[:, :, 0]\n            self.k = self.k[:, :, 0]\n            self.v = self.v[:, :, 0]\n\n        self.attn_bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=Hq,\n            num_heads_groups=Hq // Hkv,\n            q_len=Mq,\n            kv_len=Mkv,\n            dtype=dtype,\n            device=device,\n            requires_grad=False,\n            fmt=\"BMHK\",\n            op=self.OP,\n        )\n\n        if isinstance(\n            self.attn_bias,\n            xops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ):\n            self.q = self.q.view(1, -1, *self.q.shape[2:])\n            self.k = self.k.view(1, -1, *self.k.shape[2:])\n            self.v = self.v.view(1, -1, *self.v.shape[2:])\n\n        if hasattr(self.OP, \"not_supported_reasons\"):\n            inp = xops.fmha.Inputs(\n                query=self.q, key=self.k, value=self.v, attn_bias=self.attn_bias\n            )\n            not_supported_reasons = self.OP.not_supported_reasons(inp)\n            if not_supported_reasons:\n                raise NotSupportedInputError(not_supported_reasons)\n\n    def get_inputs(self):\n        inp = xops.fmha.Inputs(\n            query=self.q, key=self.k, value=self.v, attn_bias=self.attn_bias\n        )\n        return inp\n\n    def fw(self) -> None:\n        try:\n            xops.memory_efficient_attention_forward(\n                self.q, self.k, self.v, op=self.OP, attn_bias=self.attn_bias\n            )\n        except (RuntimeError, ValueError) as e:\n            print(f\"Runtime error: {e}\")\n\n\nclass AttentionDecodingCUTLASS(AttentionDecodingBase):\n    OP = xops.fmha.cutlass.FwOp\n\n\nclass AttentionDecodingCK(AttentionDecodingBase):\n    OP = xops.fmha.ck.FwOp\n\n    def __init__(\n        self,\n        B: int,\n        Mq: int,\n        Mkv: int,\n        Hq: int,\n        Hkv: int,\n        K: int,\n        bw: bool,\n        attn_bias_type,\n    ) -> None:\n        dtype = torch.float16\n        torch.manual_seed(10)\n        self.sub_label = (\n            f\"B={B} Mq={Mq} Mkv={Mkv} Hq={Hq} Hkv={Hkv} K={K} TotalBytes=\"\n            f\"{((B * Mkv * Hkv * K * 2) + (B * Mq * Hq * K) + (B * Mq * Hq * K)) * 2}\"\n        )\n        self.label = \"attn_decoding\"\n        self.shapes = (B, Mq, Mkv, Hq, Hkv, K)\n\n        assert Hkv <= Hq\n        assert Hq % Hkv == 0\n        self.q = torch.randn(\n            [B, Mq, Hkv, Hq // Hkv, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.k = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n        self.v = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n\n        if Hq == Hkv:\n            self.q = self.q[:, :, :, 0]\n            self.k = self.k[:, :, :, 0]\n            self.v = self.v[:, :, :, 0]\n\n        self.attn_bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=Hq,\n            num_heads_groups=Hq // Hkv,\n            q_len=Mq,\n            kv_len=Mkv,\n            dtype=dtype,\n            device=device,\n            requires_grad=False,\n            fmt=\"BMHK\",\n            op=self.OP,\n        )\n\n        if isinstance(\n            self.attn_bias,\n            xops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ):\n            self.q = self.q.view(1, -1, *self.q.shape[2:])\n            self.k = self.k.view(1, -1, *self.k.shape[2:])\n            self.v = self.v.view(1, -1, *self.v.shape[2:])\n\n        if hasattr(self.OP, \"not_supported_reasons\"):\n            inp = xops.fmha.Inputs(\n                query=self.q, key=self.k, value=self.v, attn_bias=self.attn_bias\n            )\n            not_supported_reasons = self.OP.not_supported_reasons(inp)\n            if not_supported_reasons:\n                raise NotSupportedInputError(not_supported_reasons)\n\n\nclass AttentionDecodingSplitKV(AttentionDecodingBase):\n    OP = xops.fmha.triton_splitk.FwOp\n\n\nclass AttentionDecodingCKSplitKV(AttentionDecodingBase):\n    OP = xops.fmha.ck_splitk.FwOp\n\n\nclass AttentionDecodingSplitInt4KV(AttentionDecodingBase):\n    OP = xops.fmha.triton_splitk.FwOp\n\n    def __init__(\n        self,\n        B: int,\n        Mq: int,\n        Mkv: int,\n        Hq: int,\n        Hkv: int,\n        K: int,\n        bw: bool,\n        attn_bias_type,\n    ) -> None:\n        # super(AttentionDecodingSplitInt4KV, self).__init__(B, Mq, Mkv, Hq, Hkv, K, bw, attn_bias_type)\n        dtype = torch.float16\n        torch.manual_seed(10)\n        self.sub_label = (\n            f\"B={B} Mq={Mq} Mkv={Mkv} Hq={Hq} Hkv={Hkv} K={K} TotalBytes=\"\n            f\"{((B * Mkv * Hkv * K * 2) + (B * Mq * Hq * K) + (B * Mq * Hq * K)) * 2}\"\n        )\n        self.label = \"attn_decoding\"\n        self.shapes = (B, Mq, Mkv, Hq, Hkv, K)\n\n        assert Hkv <= Hq\n        assert Hq % Hkv == 0\n        self.q = torch.randn(\n            [B, Mq, Hkv, Hq // Hkv, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.k = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.v = torch.randn(\n            [B, Mkv, Hkv, 1, K], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n\n        num_groups = 1\n        self.k = (\n            quantize_kv_int4(self.k, num_groups=num_groups)\n            .contiguous()\n            .view(torch.int32)\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n        self.v = (\n            quantize_kv_int4(self.v, num_groups=num_groups)\n            .contiguous()\n            .view(torch.int32)\n        ).expand(-1, -1, -1, Hq // Hkv, -1)\n\n        if Hq == Hkv:\n            self.q = self.q[:, :, :, 0]\n            self.k = self.k[:, :, :, 0]\n            self.v = self.v[:, :, :, 0]\n        if Hkv == 1:\n            self.q = self.q[:, :, 0]\n            self.k = self.k[:, :, 0]\n            self.v = self.v[:, :, 0]\n\n        self.attn_bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=Hq,\n            num_heads_groups=Hq // Hkv,\n            q_len=Mq,\n            kv_len=Mkv,\n            dtype=dtype,\n            device=device,\n            requires_grad=False,\n            fmt=\"BMHK\",\n            op=self.OP,\n        )\n\n        if isinstance(\n            self.attn_bias,\n            xops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ):\n            self.q = self.q.view(1, -1, *self.q.shape[2:])\n            self.k = self.k.view(1, -1, *self.k.shape[2:])\n            self.v = self.v.view(1, -1, *self.v.shape[2:])\n\n        if hasattr(self.OP, \"not_supported_reasons\"):\n            inp = xops.fmha.Inputs(\n                query=self.q, key=self.k, value=self.v, attn_bias=self.attn_bias\n            )\n            not_supported_reasons = self.OP.not_supported_reasons(inp)\n            if not_supported_reasons:\n                raise NotSupportedInputError(not_supported_reasons)\n\n\nclass AttentionDecodingPyTorchRepeat(AttentionDecodingBase):\n    def fw(self) -> None:\n        B, Mq, Mkv, Hq, Hkv, K = self.shapes\n        scale = 1 / K**0.5\n        q = self.q.reshape([B, Mq, -1, K]).permute(0, 2, 1, 3)\n        k = self.k.reshape([B, Mkv, -1, K]).permute(0, 2, 1, 3)\n        v = self.v.reshape([B, Mkv, -1, K]).permute(0, 2, 1, 3)\n        attn = (q @ k.transpose(-1, -2) * scale).softmax(-1)\n        return attn @ v\n\n\nBENCHMARKS: Dict[str, Type[AttentionDecodingBase]] = {\n    \"pytorch\": AttentionDecodingPyTorchRepeat,\n}\n\nif torch.version.cuda:\n    BENCHMARKS[\"cutlass\"] = AttentionDecodingCUTLASS\n\nif torch.version.hip:\n    BENCHMARKS.update(\n        {\n            \"ck\": AttentionDecodingCK,\n            \"ck_splitK\": AttentionDecodingCKSplitKV,\n        }\n    )\n\n\nif (sys.version_info.major, sys.version_info.minor) >= (3, 9):\n    BENCHMARKS[\"triton_splitK\"] = AttentionDecodingSplitKV\n    BENCHMARKS[\"triton_int4KV\"] = AttentionDecodingSplitInt4KV\n\ntry:\n    import flash_attn\n\n    class AttentionDecodingFlashAttention(AttentionDecodingBase):\n        def fw(self) -> None:\n            q, k, v = self.q, self.k, self.v\n            if q.ndim == 5:\n                B, Mq, H1, H2, K = q.shape\n                B, Mkv, H1, H2, K = k.shape\n                q = q.reshape([B, Mq, H1 * H2, K])\n                k = k[:, :, :, 0]\n                v = v[:, :, :, 0]\n            return flash_attn.flash_attn_func(q, k, v)\n\n    BENCHMARKS[f\"flash-attention@{flash_attn.__version__}\"] = (\n        AttentionDecodingFlashAttention\n    )\nexcept ImportError:\n    pass\n\n\nTEST_CASES = [\n    dict(\n        B=max(1, 2 ** (16 - i)),\n        Mq=1,\n        Mkv=2**i,\n        Hq=16,\n        Hkv=hkv,\n        K=128,\n        attn_bias_type=None,\n    )\n    for i in range(8, 18)\n    for hkv in range(1, 3)\n] + [\n    dict(B=i, Mq=1, Mkv=4097, Hq=8, Hkv=1, K=128, attn_bias_type=None)\n    for i in [2, 4, 8, 16, 32, 64, 128]\n]\n\n\ndef get_benchmark_names():\n    decoder_names = list(BENCHMARKS.keys())\n    decoder_names.remove(\"pytorch\")\n    return decoder_names\n\n\n# tests to verify correctness of each decoder implementation\n@pytest.mark.parametrize(\n    \"name, case\",\n    [(name, case) for name in get_benchmark_names() for case in TEST_CASES],\n)\ndef test_flash_attention_decoder(name, case):\n    baseline = AttentionDecodingPyTorchRepeat(\n        case[\"B\"],\n        case[\"Mq\"],\n        case[\"Mkv\"],\n        case[\"Hq\"],\n        case[\"Hkv\"],\n        case[\"K\"],\n        False,\n        case[\"attn_bias_type\"],\n    )\n    if name == \"ck-decoder\" and case[\"Mkv\"] >= 2**14:\n        pytest.skip(\"ck-decoder does not support Mkv >= 16K\")\n\n    baseline_out = baseline.fw()\n    inputs = baseline.get_inputs()\n    decoder = BENCHMARKS[name]\n\n    assert name in [\"ck_splitK\", \"ck\", \"triton_splitK\", \"triton_int4KV\"]\n    decoder_output, ctx = decoder.OP.apply(inputs, False)\n\n    q, k, v = inputs.get_qkv_in_bmghk()\n    B, M, G, H, Kq = q.shape\n    mqa_swap_seqlen_head = False\n    if k.shape[3] > 1 and k.stride(3) == 0 and v.stride(3) == 0:\n        mqa_swap_seqlen_head = True\n    if mqa_swap_seqlen_head:\n        decoder_output = (\n            decoder_output.reshape(B, -1, M, Kq).transpose(1, 2).contiguous()\n        )\n    else:\n        decoder_output = decoder_output.reshape(B, H * G, -1, Kq).contiguous()\n\n    decoder_output = decoder_output.transpose(2, 1).contiguous()\n    torch.testing.assert_close(decoder_output, baseline_out, atol=1e-2, rtol=0)\n\n\ndef main() -> None:\n    \"\"\"\n    run performance benchmark\n    \"\"\"\n    benchmark_main_helper2(\n        \"attn_decoding\",\n        fw=True,\n        cases=CASES,\n        functions=BENCHMARKS,\n        min_run_time=min_run_time,\n    )\n\n\nif __name__ == \"__main__\":\n    main()  # pragma: no cover\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_indexing.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport random\n\nimport torch\n\nimport xformers.ops as xops\nfrom utils import benchmark_main_helper2, DTYPE2STR, product_dict\n\nmin_run_time = 0.2\ndevice = torch.device(\"cuda\")\n\nCASES_IADD = list(\n    product_dict(\n        shape=[\n            (int(48 * 0.6), 48, 1, 257 * 1536),\n            (int(48 * 0.6), 48, 257, 1536),\n        ],\n        scaling=[False, True],\n        dtype=[torch.half],\n    )\n) + list(\n    product_dict(\n        shape=[\n            # Format: [B_src, B_inp, M, D]\n            (int(192 * 0.6), 192, 50, 1536),\n            (int(48 * 257 * 0.6), 257 * 48, 1, 1536),\n            (int(192 * 50 * 0.6), 192 * 50, 1, 1536),\n            (int(16 * 257 * 0.6), 48 * 257, 1, 1536),\n        ],\n        scaling=[False],\n        dtype=[torch.half],\n    )\n)\n\nCASES_ISELECT = list(\n    product_dict(\n        batches=[((48, 257), (50, 192))],\n        D=[1536],\n        keep_ratio=[0.6],\n        dtype=[torch.half],\n    )\n)\n\n\nclass ScaledIndexAddBenchmark:\n    def __init__(self, dtype, scaling: bool, shape, bw: bool) -> None:\n        B_src, B_out, M, D = shape\n        torch.manual_seed(B_out + B_src)\n        dtype_str = DTYPE2STR.get(dtype, dtype)\n        self.sub_label = f\"{dtype_str} B_src={B_src}, B_out={B_out}, M={M}, D={D} s={'Y' if scaling else 'N'}\"\n        self.label = \"scaled_index_add\"\n        self.alpha = 0.73\n\n        self.inp = torch.randn(\n            [B_out, M, D], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.src = torch.randn(\n            [B_src, M, D], device=\"cuda\", dtype=dtype, requires_grad=bw\n        )\n        self.scaling = (\n            torch.randn([D], device=\"cuda\", dtype=dtype, requires_grad=bw)\n            if scaling\n            else None\n        )\n        self.index = torch.tensor(\n            [i for i in range(self.src.shape[0])], dtype=torch.int64, device=\"cuda\"\n        )\n        self.grad = torch.randn([B_out, M, D], device=\"cuda\", dtype=dtype)\n        self.out = torch.Tensor()\n\n    def fw(self) -> None:\n        self.out = xops.scaled_index_add(\n            input=self.inp.clone(),\n            index=self.index,\n            source=self.src,\n            scaling=self.scaling,\n            alpha=self.alpha,\n        )\n\n    def bw(self):\n        self.inp.grad = None\n        self.src.grad = None\n        if self.scaling is not None:\n            self.scaling.grad = None\n        self.out.backward(self.grad, retain_graph=True)\n\n\nclass ScaledIndexAddBenchmarkBaseline(ScaledIndexAddBenchmark):\n    def fw(self) -> None:\n        src_scaled = self.src\n        if self.scaling is not None:\n            src_scaled * self.scaling.unsqueeze(0).unsqueeze(0)\n        self.out = self.inp.index_add(\n            dim=0,\n            source=src_scaled,\n            index=self.index,\n            alpha=self.alpha,\n        )\n\n\nclass IndexSelectBenchmark:\n    def __init__(self, dtype, batches, D, keep_ratio, bw: bool) -> None:\n        dtype_str = DTYPE2STR.get(dtype, dtype)\n        self.sub_label = f\"{dtype_str} D={D} batches={batches} keep={keep_ratio}\"\n        self.label = \"index_select\"\n\n        indices = []\n        sources = []\n        for B, seqlen in batches:\n            index = [i for i in range(B)]\n            random.Random(B).shuffle(index)\n            indices.append(\n                torch.zeros(\n                    index[int(keep_ratio * B)],\n                    dtype=torch.int64,\n                    device=\"cuda\",\n                )\n            )\n            source_i = torch.randn(\n                [B, seqlen * D], dtype=dtype, device=\"cuda\", requires_grad=bw\n            )\n            sources.append(source_i)\n        self.indices, self.sources = indices, sources\n        self.out = torch.Tensor()\n\n    def fw(self) -> None:\n        self.out = xops.index_select_cat(self.sources, self.indices)\n\n    def bw(self):\n        for src in self.sources:\n            src.grad = None\n        self.out.backward(self.out, retain_graph=True)\n\n\nclass IndexSelectBenchmarkBaseline(IndexSelectBenchmark):\n    def fw(self) -> None:\n        self.out = torch.cat(\n            [s[i].flatten() for s, i in zip(self.sources, self.indices)], dim=0\n        )\n\n\nbenchmark_main_helper2(\n    \"scaled_index_add_fw\",\n    fw=True,\n    functions={\n        \"xformers\": ScaledIndexAddBenchmark,\n        \"pytorch\": ScaledIndexAddBenchmarkBaseline,\n    },\n    cases=CASES_IADD,\n    min_run_time=min_run_time,\n)\n\nbenchmark_main_helper2(\n    \"scaled_index_add_fwbw\",\n    fw=True,\n    bw=True,\n    functions={\n        \"xformers\": ScaledIndexAddBenchmark,\n        \"pytorch\": ScaledIndexAddBenchmarkBaseline,\n    },\n    cases=CASES_IADD,\n    min_run_time=min_run_time,\n)\n\nbenchmark_main_helper2(\n    \"index_select_fw\",\n    fw=True,\n    functions={\n        \"xformers\": IndexSelectBenchmark,\n        \"pytorch\": IndexSelectBenchmarkBaseline,\n    },\n    cases=CASES_ISELECT,\n    min_run_time=min_run_time,\n)\n\nbenchmark_main_helper2(\n    \"index_select_fwbw\",\n    fw=True,\n    bw=True,\n    functions={\n        \"xformers\": IndexSelectBenchmark,\n        \"pytorch\": IndexSelectBenchmarkBaseline,\n    },\n    cases=CASES_ISELECT,\n    min_run_time=min_run_time,\n)\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_mem_eff_attention.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport itertools\nimport random\nfrom functools import partial\n\nimport torch\n\nimport xformers.ops\nimport xformers.ops.fmha as fmha\nfrom torch.utils import benchmark\nfrom xformers.attn_bias_utils import create_attn_bias, ref_attention\nfrom xformers.benchmarks.utils import benchmark_main_helper, create_argparser\n\ntorch.backends.cuda.matmul.allow_tf32 = False\n\nmin_run_time = 0.5\ndevice = torch.device(\"cuda\")\n\nNUM_THREADS = [1] if device.type == \"cuda\" else [1, 40]\nVISION_SHAPES = [\n    # ViT\n    (384, 197, 1, 88),\n    (384, 197, 1, 80),\n    (384, 197, 1, 64),\n    (1024, 197, 1, 88),\n    (1024, 197, 1, 80),\n    (1024, 197, 1, 64),\n    # ViT-Huge\n    (32 * 16, 197, 1, 80),\n    (32, 197, 16, 80),\n    (32, 197, 16, 64),\n    (32, 197, 16, 128),\n    # ViT-Giant\n    (16 * 16, 197, 1, 88),\n    (16, 197, 16, 88),\n    (16, 197, 16, 64),\n    (16, 197, 16, 128),\n    # FB models\n    (1024, 82, 8, 64),\n    (150, 256, 16, 64),\n    (64, 256, 12, 64),\n    # Stable diffusion (https://github.com/huggingface/diffusers/pull/532)\n    (1, 4096, 16, 40),  # 512x512\n    (1, 16384, 16, 40),  # 1024x1024\n    (1, 4096, 16, 80),\n    (1, 16384, 16, 80),\n    # + bs4\n    (4, 4096, 16, 40),\n    (4, 16384, 16, 40),\n    (4, 4096, 16, 80),\n    (4, 16384, 16, 80),\n    # ParlAI model\n    (256, 4096, 16, 64),\n    # Zetta B M H K\n    (8, 2048, 20, 128),\n]\n\nLLM_SHAPES = [\n    # LLaMa 70b - mp=8/16\n    *sorted(itertools.product([1, 2], [2048, 4096, 8192], [4, 8], [128])),\n    *sorted(\n        itertools.product([16], [128, 512, 1024], [16], [16, 32, 64, 128, 160, 256])\n    ),\n]\n\n\nOPS = [\n    (xformers.ops.fmha.cutlass.FwOp, xformers.ops.fmha.cutlass.BwOp),\n    (xformers.ops.fmha.flash.FwOp, xformers.ops.fmha.flash.BwOp),\n    (xformers.ops.fmha.flash3.FwOp, xformers.ops.fmha.flash3.BwOp),\n    (xformers.ops.fmha.ck.FwOp, xformers.ops.fmha.ck.BwOp),\n]\n\n\ndef product_dict(**kwargs):\n    keys = kwargs.keys()\n    vals = kwargs.values()\n    for instance in itertools.product(*vals):\n        yield dict(zip(keys, instance))\n\n\nVISION_CASES, LLM_CASES = [\n    list(\n        product_dict(\n            shape_q=SHAPES,\n            num_threads=NUM_THREADS,\n            dropout_p=[0.0],\n            attn_bias_cfg=[(type(None), False)],\n            dtype=[torch.half],\n        )\n    )\n    for SHAPES in (VISION_SHAPES, LLM_SHAPES)\n]\n\n# Add more cases with some variations\nfor c in VISION_CASES.copy():\n    c = c.copy()\n    c.update(\n        random.Random(str(c[\"shape_q\"])).choice(\n            [\n                {\"dropout_p\": 0.3},\n                {\"attn_bias_cfg\": (torch.Tensor, False)},\n                {\"attn_bias_cfg\": (torch.Tensor, True)},\n                {\"dtype\": torch.bfloat16},\n                {\"dtype\": torch.float},\n            ]\n        )\n    )\n    VISION_CASES.append(c)\n\n\nLLM_CASE_UPDATES = [\n    {\"attn_bias_cfg\": (torch.Tensor, True)},\n    {\"attn_bias_cfg\": (xformers.ops.LowerTriangularMask, False)},\n    *[\n        {\n            \"attn_bias_cfg\": (\n                xformers.ops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n                False,\n            ),\n            \"Hkv\": Hkv,\n            \"dtype\": torch.bfloat16,\n        }\n        for Hkv in [1, 2]\n    ],\n]\n\nfor c in LLM_CASES.copy():\n    for update in LLM_CASE_UPDATES:\n        c = c.copy()\n        c.update(update)\n        LLM_CASES.append(c)\n\nCASES = VISION_CASES + LLM_CASES\n\n\ndef create_tensors(shape_q, Hkv, dtype, requires_grad=False, packed=True):\n    stacked_shape = list(shape_q)  # B, M, H, K\n    Hq = shape_q[2]\n    stacked_dim = 2 if packed else 0\n    stacked_shape.insert(stacked_dim, 3)\n    qkv = torch.rand(\n        stacked_shape, device=device, dtype=dtype, requires_grad=requires_grad\n    )\n    q = torch.rand(shape_q, device=device, dtype=dtype, requires_grad=requires_grad)\n    shape_kv = (shape_q[0], shape_q[1], Hkv, shape_q[3])\n    k = (\n        torch.rand(shape_kv, device=device, dtype=dtype, requires_grad=requires_grad)\n        .reshape(shape_q[0], shape_q[1], 1, Hkv, shape_q[3])\n        .expand(shape_q[0], shape_q[1], Hq // Hkv, Hkv, shape_q[3])\n        .reshape(shape_q)\n    )\n    v = (\n        torch.rand(shape_kv, device=device, dtype=dtype, requires_grad=requires_grad)\n        .reshape(shape_q[0], shape_q[1], 1, Hkv, shape_q[3])\n        .expand(shape_q[0], shape_q[1], Hq // Hkv, Hkv, shape_q[3])\n        .reshape(shape_q)\n    )\n\n    return qkv, q, k, v\n\n\ndef mem_eff_attention_fw(\n    shape_q,\n    num_threads: int,\n    attn_bias_cfg,\n    dropout_p,\n    dtype,\n    packed=True,\n    Hkv=None,\n):\n    B, M, Hq, K = shape_q\n    Hkv = Hkv or Hq\n    _, q, k, v = create_tensors(\n        shape_q,\n        Hkv,\n        dtype,\n        requires_grad=False,\n        packed=packed,\n    )\n    attn_bias_type, attn_bias_requires_grad = attn_bias_cfg\n    if attn_bias_requires_grad:\n        return\n\n    dtype_str = {\n        torch.bfloat16: \"b16\",\n        torch.half: \"f16\",\n        torch.float: \"f32\",\n    }[dtype]\n    sub_label = (\n        f\"{dtype_str} {B}-{M}-{Hq}-{Hkv}-{K}, p={dropout_p}, \"\n        f\"BiasT={attn_bias_type.__name__}\"\n    )\n\n    has_run = False\n    for fw_op, bw_op in OPS:\n        bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=Hq,\n            num_heads_groups=Hq // Hkv,\n            q_len=M,\n            kv_len=M,\n            dtype=dtype,\n            device=device,\n            requires_grad=attn_bias_requires_grad,\n            fmt=\"BMHK\",\n            op=fw_op,\n        )\n        inp = fmha.Inputs(query=q, key=k, value=v, attn_bias=bias, p=dropout_p)\n        if isinstance(\n            bias,\n            (\n                fmha.attn_bias.BlockDiagonalMask,\n                fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            ),\n        ):\n            q, k, v = [x.reshape([1, -1, *x.shape[2:]]) for x in [q, k, v]]\n        if not fw_op.supports(inp):\n            continue\n\n        yield benchmark.Timer(\n            stmt=\"fn(q, k, v, attn_bias, p)\",\n            globals={\n                \"q\": q,\n                \"k\": k,\n                \"v\": v,\n                \"attn_bias\": inp.attn_bias,\n                \"p\": dropout_p,\n                \"fn\": partial(\n                    xformers.ops.memory_efficient_attention, op=(fw_op, bw_op)\n                ),\n            },\n            label=f\"attention (attn_bias={attn_bias_type})\",\n            description=fw_op.NAME,\n            sub_label=sub_label,\n            num_threads=num_threads,\n        )\n        has_run = True\n\n    if not has_run:\n        return\n\n    yield benchmark.Timer(\n        stmt=\"fn(q, k, v, attn_bias, p)\",\n        globals={\n            \"q\": q,\n            \"k\": k,\n            \"v\": v,\n            \"attn_bias\": inp.attn_bias,\n            \"p\": dropout_p,\n            \"fn\": ref_attention,\n        },\n        label=f\"attention (attn_bias={attn_bias_type})\",\n        description=\"eager\",\n        sub_label=sub_label,\n        num_threads=num_threads,\n    )\n\n\ndef mem_eff_attention_bw(\n    shape_q, num_threads: int, attn_bias_cfg, dropout_p, dtype, Hkv=None\n):\n    B, M, Hq, K = shape_q\n    Hkv = Hkv or Hq\n    _, q, k, v = create_tensors(\n        shape_q,\n        Hkv,\n        dtype,\n        requires_grad=True,\n    )\n\n    attn_bias_type, attn_bias_requires_grad = attn_bias_cfg\n\n    dtype_str = {\n        torch.bfloat16: \"b16\",\n        torch.half: \"f16\",\n        torch.float: \"f32\",\n    }[dtype]\n    sub_label = (\n        f\"{dtype_str} {B}-{M}-{Hq}-{Hkv}-{K}, p={dropout_p}, \"\n        f\"BiasT={attn_bias_type.__name__}, BiasGrad={attn_bias_requires_grad}\"\n    )\n\n    has_run = False\n    for fw_op, bw_op in OPS:\n        bias = create_attn_bias(\n            attn_bias_type,\n            batch_size=B,\n            num_heads=Hq,\n            num_heads_groups=Hq // Hkv,\n            q_len=M,\n            kv_len=M,\n            dtype=dtype,\n            device=device,\n            requires_grad=attn_bias_requires_grad,\n            fmt=\"BMHK\",\n            op=bw_op,\n        )\n        inp = fmha.Inputs(query=q, key=k, value=v, attn_bias=bias, p=dropout_p)\n\n        if not fw_op.supports(inp) or not bw_op.supports(inp):\n            continue\n        has_run = True\n        out = xformers.ops.memory_efficient_attention(\n            inp.query, inp.key, inp.value, inp.attn_bias, inp.p, op=(fw_op, bw_op)\n        )\n        grad_benchmark = torch.ones_like(q)\n\n        yield benchmark.Timer(\n            stmt=\"out.backward(grad, retain_graph=True)\",\n            globals={\n                \"out\": out,\n                \"grad\": grad_benchmark,\n            },\n            label=f\"attention backward (attn_bias={attn_bias_type})\",\n            description=bw_op.NAME,\n            sub_label=sub_label,\n            num_threads=num_threads,\n        )\n        del out\n\n    if not has_run:\n        return\n    yield benchmark.Timer(\n        stmt=\"out.backward(grad, retain_graph=True)\",\n        globals={\n            \"out\": ref_attention(q, k, v, inp.attn_bias, dropout_p),\n            \"grad\": grad_benchmark,\n        },\n        label=f\"attention backward (attn_bias={attn_bias_type})\",\n        description=\"vanilla\",\n        sub_label=sub_label,\n        num_threads=num_threads,\n    )\n\n\ndef main():\n    arg_parser = create_argparser()\n    arg_parser.add_argument(\n        \"--omit-forward\",\n        action=\"store_true\",\n        help=\"Do not run forward benchmarks\",\n    )\n    arg_parser.add_argument(\n        \"--omit-backward\",\n        action=\"store_true\",\n        help=\"Do not run backward benchmarks\",\n    )\n    args = arg_parser.parse_args()\n    if not args.omit_forward:\n        benchmark_main_helper(\n            mem_eff_attention_fw,\n            CASES,\n            arg_parser=arg_parser,\n            min_run_time=min_run_time,\n        )\n    if not args.omit_backward:\n        benchmark_main_helper(\n            mem_eff_attention_bw,\n            CASES,\n            arg_parser=arg_parser,\n            min_run_time=min_run_time,\n        )\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_merge_attentions.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport torch\n\nfrom xformers.ops import fmha\nfrom xformers.utils import do_bench_cudagraph\n\n\ndef _merge_attentions_varargs_ref(attn_split, lse_split):\n    \"\"\"\n    attn_split: list of [B, M, (G,) H, Kq]\n    lse_split: list of [B, (G,) H, M]\n    \"\"\"\n    attn_split = torch.stack(attn_split)\n    lse_split = torch.stack(lse_split)\n\n    lse_split = lse_split[..., None].moveaxis(4, 2)  # [split_k, B, M, G, H, 1]\n\n    lse_max, _ = torch.max(lse_split, dim=0)  # [B, M, G, H, 1]\n    sumexp_normalized = torch.exp(lse_split - lse_max)  # [split_k, B, M, G, H, 1]\n    denominator = sumexp_normalized.sum(dim=0)  # [B, M, G, H, 1]\n    numerator = (sumexp_normalized * attn_split).sum(dim=0)  # [B, M, G, H, K]\n\n    attn_out = numerator / denominator  # [B, M_ceil, G, H, Kq]\n    lse_out = lse_max + torch.log(denominator)\n    lse_out = lse_out.squeeze(4).permute(0, 2, 3, 1)  # [B, G, H, M]\n\n    return attn_out, lse_out\n\n\ndef benchmark_merge_attentions_backward(split_k, B, M, G, N_H_L, D_H, dtype):\n    \"\"\"\n    Benchmark backward pass for merge_attentions. Assumes \"varargs\" path,\n    i.e. LSE and attention of chunks are provided as two lists of tensors, and not as two stacked tensors.\n    \"\"\"\n\n    bench_stream = torch.cuda.Stream()\n    with torch.cuda.stream(bench_stream):\n\n        attn_split = [\n            torch.randn(\n                [B, M, G, N_H_L, D_H], dtype=dtype, device=\"cuda\", requires_grad=True\n            )\n            for _ in range(split_k)\n        ]\n        lse_split = [\n            torch.randn(\n                [B, G, N_H_L, M], dtype=dtype, device=\"cuda\", requires_grad=True\n            )\n            for _ in range(split_k)\n        ]\n\n        attn_out_ref, lse_out_ref = _merge_attentions_varargs_ref(attn_split, lse_split)\n        out_grad = torch.randn_like(attn_out_ref)\n        attn_out_ref.backward(out_grad, retain_graph=True)\n        t_ms_ref = do_bench_cudagraph(\n            lambda: attn_out_ref.backward(out_grad, retain_graph=True)\n        )\n\n        for x in attn_split + lse_split:\n            x.detach_()\n            x.requires_grad_(True)\n\n        attn_out, lse_out = fmha.merge_attentions(attn_split, lse_split)\n        attn_out.backward(out_grad, retain_graph=True)\n        t_ms = do_bench_cudagraph(\n            lambda: attn_out.backward(out_grad, retain_graph=True)\n        )\n\n        print(\n            f\"{split_k=}, {B=}, {M=}, {G=}, {N_H_L=}, {D_H=}, {dtype=}. \"\n            f\"Baseline: {t_ms_ref * 1e3:.2f}us, \"\n            f\"Triton: {t_ms * 1e3:.2f}us, {t_ms_ref/t_ms:.1f}x faster\"\n        )\n\n\ndef main():\n    G = 2\n    N_H_L = 8\n    D_H = 128\n    dtype = torch.float32\n    for split_k in [2, 4, 8, 16]:\n        for B in [1, 32, 128]:\n            for M in [1, 32, 512]:\n                benchmark_merge_attentions_backward(split_k, B, M, G, N_H_L, D_H, dtype)\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_sequence_parallel_fused.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport contextlib\nimport dataclasses\nimport enum\nimport multiprocessing\nimport os\nimport random\nfrom collections import deque\nfrom statistics import mean, stdev\nfrom typing import Callable\n\nimport torch\n\n# torch._C._set_print_stack_traces_on_fatal_signal(True)\n\n\n@dataclasses.dataclass\nclass Scenario:\n    # The number of tokens, i.e., the batch size times the sequence length\n    num_samples: int\n    # The per-sample features outside of the MHA/FFN block, and inside of it\n    outer_dim: int\n    inner_dim: int\n    # Simulate this many matmuls during the all-gather step\n    num_ag_matrices: int\n\n\nclass Step(enum.Enum):\n    AllGather = \"ag\"\n    ReduceScatter = \"rs\"\n\n    def __str__(self):\n        return self.value\n\n\n@dataclasses.dataclass\nclass Bench:\n    ag: Callable[[], None]\n    rs: Callable[[], None]\n\n    def __getitem__(self, step: Step):\n        if step is Step.AllGather:\n            return self.ag\n        elif step is Step.ReduceScatter:\n            return self.rs\n        else:\n            raise KeyError(f\"{step}\")\n\n\nLLAMA_07B_SLEN = 4096\nLLAMA_07B_D = 4096\n\nLLAMA_70B_SLEN = 2048\nLLAMA_70B_D = 8192\n\n\ndef round_up_to_nearest_multiple(n: int, m: int) -> int:\n    return m * ((n + m - 1) // m)\n\n\ndef llama_07B_MHA(world_size: int) -> Scenario:\n    batch_size = 8\n    return Scenario(\n        num_samples=batch_size * LLAMA_07B_SLEN,\n        outer_dim=LLAMA_07B_D,\n        inner_dim=LLAMA_07B_D // world_size,\n        num_ag_matrices=3,\n    )\n\n\ndef llama_07B_FFN(world_size: int) -> Scenario:\n    batch_size = 8\n    return Scenario(\n        num_samples=batch_size * LLAMA_07B_SLEN,\n        outer_dim=LLAMA_07B_D,\n        inner_dim=round_up_to_nearest_multiple(2 * (4 * LLAMA_07B_D) // 3, 256)\n        // world_size,\n        num_ag_matrices=2,\n    )\n\n\ndef llama_70B_MHA(world_size: int) -> Scenario:\n    batch_size = world_size\n    return Scenario(\n        num_samples=batch_size * LLAMA_70B_SLEN,\n        outer_dim=LLAMA_70B_D,\n        inner_dim=LLAMA_70B_D // world_size,\n        num_ag_matrices=3,\n    )\n\n\ndef llama_70B_FFN(world_size: int) -> Scenario:\n    batch_size = world_size\n    return Scenario(\n        num_samples=batch_size * LLAMA_70B_SLEN,\n        outer_dim=LLAMA_70B_D,\n        inner_dim=round_up_to_nearest_multiple(2 * (4 * LLAMA_70B_D) // 3, 256)\n        // world_size,\n        num_ag_matrices=2,\n    )\n\n\nSCENARIOS = {\n    \"llama_07B_MHA\": llama_07B_MHA,\n    \"llama_07B_FFN\": llama_07B_FFN,\n    \"llama_70B_MHA\": llama_70B_MHA,\n    \"llama_70B_FFN\": llama_70B_FFN,\n}\n\nDTYPES = {\n    \"bfloat16\": torch.bfloat16,\n}\n\n\ndef run_one_rank(\n    my_rank,\n    world_size,\n    scenario_name,\n    step,\n    dtype_str,\n    num_rounds,\n    num_warmup_iters,\n    num_bench_iters,\n    profile,\n    conn_from_prev,\n    conn_to_next,\n):\n    print(f\"RANK {my_rank} started\")\n\n    torch.cuda.set_device(my_rank)\n    my_device = torch.device(f\"cuda:{my_rank}\")\n\n    os.environ[\"RANK\"] = f\"{my_rank}\"\n    os.environ[\"WORLD_SIZE\"] = f\"{world_size}\"\n    os.environ[\"MASTER_ADDR\"] = \"localhost\"\n    os.environ[\"MASTER_PORT\"] = \"29500\"\n    torch.distributed.init_process_group(backend=\"nccl\", init_method=\"env://\")\n\n    subgroup = torch.distributed.new_group()\n    subgroup_nowait = torch.distributed.new_group()\n    subgroup_nowait_nomemcpy = torch.distributed.new_group()\n\n    scenario = SCENARIOS[scenario_name](world_size)\n    if step is Step.AllGather:\n        M = scenario.num_samples\n        N = scenario.inner_dim\n        K = scenario.outer_dim\n        num_matrices = scenario.num_ag_matrices\n    elif step is Step.ReduceScatter:\n        M = scenario.num_samples\n        N = scenario.outer_dim\n        K = scenario.inner_dim\n        num_matrices = 1\n\n    dtype = DTYPES[dtype_str]\n\n    scattered_input = torch.randn((M // world_size, K), dtype=dtype, device=my_device)\n    gathered_input = torch.randn((M, K), dtype=dtype, device=my_device)\n    weights = [\n        torch.randn((K, N), dtype=dtype, device=my_device) for _ in range(num_matrices)\n    ]\n    gathered_outputs = [\n        torch.randn((M, N), dtype=dtype, device=my_device) for _ in range(num_matrices)\n    ]\n    scattered_outputs = [\n        torch.randn((M // world_size, N), dtype=dtype, device=my_device)\n        for _ in range(num_matrices)\n    ]\n\n    gathered_outputs_nccl_reference = [\n        torch.randn((M, N), dtype=dtype, device=my_device) for _ in range(num_matrices)\n    ]\n    gathered_outputs_fused = [\n        torch.randn((M, N), dtype=dtype, device=my_device) for _ in range(num_matrices)\n    ]\n    scattered_outputs_nccl_reference = [\n        torch.randn((M // world_size, N), dtype=dtype, device=my_device)\n        for _ in range(num_matrices)\n    ]\n    scattered_outputs_fused = [\n        torch.randn((M // world_size, N), dtype=dtype, device=my_device)\n        for _ in range(num_matrices)\n    ]\n\n    def run_compute_lower_bound_ag():\n        for w, go in zip(weights, gathered_outputs):\n            torch.matmul(gathered_input, w, out=go)\n\n    def run_compute_lower_bound_rs():\n        for w, go, so in zip(weights, gathered_outputs, scattered_outputs):\n            torch.matmul(gathered_input, w, out=go)\n            torch.sum(go.view((world_size, M // world_size, N)), dim=0, out=so)\n\n    def run_comms_lower_bound_ag():\n        torch.distributed.all_gather_into_tensor(gathered_input, scattered_input)\n\n    def run_comms_lower_bound_rs():\n        for so, go in zip(scattered_outputs, gathered_outputs):\n            torch.distributed.reduce_scatter_tensor(so, go)\n\n    def run_nccl_reference_ag():\n        torch.distributed.all_gather_into_tensor(gathered_input, scattered_input)\n        for w, go in zip(weights, gathered_outputs_nccl_reference):\n            torch.matmul(gathered_input, w, out=go)\n\n    def run_nccl_reference_rs():\n        for w, go, so in zip(\n            weights, gathered_outputs, scattered_outputs_nccl_reference\n        ):\n            torch.matmul(gathered_input, w, out=go)\n            torch.distributed.reduce_scatter_tensor(so, go)\n\n    def run_fused_ag():\n        nonlocal gathered_outputs_fused\n        from xformers.ops import fused_allgather_and_linear\n\n        gathered_outputs_fused = fused_allgather_and_linear(\n            scattered_input,\n            [w.t() for w in weights],\n            group=subgroup,\n            timeout_s=10,\n        )\n\n    def run_fused_rs():\n        nonlocal scattered_outputs_fused\n        from xformers.ops import fused_linear_and_reducescatter\n\n        scattered_outputs_fused = fused_linear_and_reducescatter(\n            gathered_input,\n            [w.t() for w in weights],\n            group=subgroup,\n            timeout_s=10,\n        )\n\n    def run_fused_nowait_ag():\n        nonlocal gathered_outputs_fused\n        from xformers.ops import fused_allgather_and_linear\n\n        gathered_outputs_fused = fused_allgather_and_linear(\n            scattered_input,\n            [w.t() for w in weights],\n            group=subgroup_nowait,\n            _wait=False,\n            timeout_s=10,\n        )\n\n    def run_fused_nowait_rs():\n        nonlocal scattered_outputs_fused\n        from xformers.ops import fused_linear_and_reducescatter\n\n        scattered_outputs_fused = fused_linear_and_reducescatter(\n            gathered_input,\n            [w.t() for w in weights],\n            group=subgroup_nowait,\n            _wait=False,\n            timeout_s=10,\n        )\n\n    def run_fused_nowait_nomemcpy_ag():\n        nonlocal gathered_outputs_fused\n        from xformers.ops import fused_allgather_and_linear\n\n        gathered_outputs_fused = fused_allgather_and_linear(\n            scattered_input,\n            [w.t() for w in weights],\n            group=subgroup_nowait_nomemcpy,\n            _wait=False,\n            _memcpy=False,\n            timeout_s=10,\n        )\n\n    def run_fused_nowait_nomemcpy_rs():\n        nonlocal scattered_outputs_fused\n        from xformers.ops import fused_linear_and_reducescatter\n\n        scattered_outputs_fused = fused_linear_and_reducescatter(\n            gathered_input,\n            [w.t() for w in weights],\n            group=subgroup_nowait_nomemcpy,\n            _wait=False,\n            _memcpy=False,\n            timeout_s=10,\n        )\n\n    print(f\"Sizes: ({world_size}x{M // world_size})x({num_matrices}x{N})x{K}\")\n\n    if step is Step.AllGather:\n        run_nccl_reference_ag()\n        run_fused_ag()\n        if my_rank == 0:\n            print(\"fused:\")\n            print(\n                \"Are equal? \"\n                + \" \".join(\n                    str(torch.equal(ref, fus))\n                    for ref, fus in zip(\n                        gathered_outputs_nccl_reference, gathered_outputs_fused\n                    )\n                )\n            )\n            print(\n                \"Are allclose? \"\n                + \" \".join(\n                    str(torch.allclose(ref, fus))\n                    for ref, fus in zip(\n                        gathered_outputs_nccl_reference, gathered_outputs_fused\n                    )\n                )\n            )\n\n    elif step is Step.ReduceScatter:\n        run_nccl_reference_rs()\n        run_fused_rs()\n        if my_rank == 0:\n            print(\"fused:\")\n            print(\n                \"Are equal? \"\n                + \" \".join(\n                    str(torch.equal(ref, fus))\n                    for ref, fus in zip(\n                        scattered_outputs_nccl_reference, scattered_outputs_fused\n                    )\n                )\n            )\n            print(\n                \"Are allclose? \"\n                + \" \".join(\n                    str(torch.allclose(ref, fus))\n                    for ref, fus in zip(\n                        scattered_outputs_nccl_reference, scattered_outputs_fused\n                    )\n                )\n            )\n\n    # The above checks might still return False for, e.g., bfloat16 because they\n    # have too little tolerance for its lower precision. This method, OTOH, uses\n    # variable tolerances based on dtype.\n    # for ref, fus in zip(gathered_outputs_nccl_reference, gathered_outputs_fused):\n    #     torch.testing.assert_close(ref, fus)\n    # for ref, fus in zip(scattered_outputs_nccl_reference, scattered_outputs_fused):\n    #     torch.testing.assert_close(ref, fus)\n\n    all_benchs = {\n        \"compute_lower_bound\": Bench(\n            ag=run_compute_lower_bound_ag, rs=run_compute_lower_bound_rs\n        ),\n        \"comms_lower_bound\": Bench(\n            ag=run_comms_lower_bound_ag, rs=run_comms_lower_bound_rs\n        ),\n        \"nccl_reference\": Bench(ag=run_nccl_reference_ag, rs=run_nccl_reference_rs),\n        \"fused\": Bench(ag=run_fused_ag, rs=run_fused_rs),\n        \"fused_nowait\": Bench(ag=run_fused_nowait_ag, rs=run_fused_nowait_rs),\n        \"fused_nowait_nomemcpy\": Bench(\n            ag=run_fused_nowait_nomemcpy_ag, rs=run_fused_nowait_nomemcpy_rs\n        ),\n    }\n\n    unused_events = deque(\n        tuple(torch.cuda.Event(enable_timing=my_rank == 0) for _ in range(2))\n        for f in range(len(all_benchs))\n    )\n    used_events = deque()\n\n    timings = {}\n\n    gen = random.Random(42)\n\n    if profile:\n        profiler = torch.profiler.profile()\n    else:\n        profiler = contextlib.nullcontext()\n\n    with profiler as p:\n        for method in gen.sample(\n            list(all_benchs),\n            k=num_rounds * len(all_benchs),\n            counts=[num_rounds] * len(all_benchs),\n        ):\n            fun = all_benchs[method][step]\n\n            if unused_events:\n                start_ev, end_ev = unused_events.popleft()\n            else:\n                old_method, start_ev, end_ev = used_events.popleft()\n                end_ev.synchronize()\n                if my_rank == 0:\n                    timings.setdefault(old_method, []).append(\n                        start_ev.elapsed_time(end_ev) / num_bench_iters\n                    )\n\n            for _ in range(num_warmup_iters):\n                fun()\n            start_ev.record()\n            for _ in range(num_bench_iters):\n                fun()\n            end_ev.record()\n\n            used_events.append((method, start_ev, end_ev))\n\n        torch.cuda.synchronize()\n\n    if profile:\n        p.export_chrome_trace(f\"fusion_trace_{my_rank}.json\")\n\n    if my_rank == 0:\n        for method, start_ev, end_ev in used_events:\n            timings.setdefault(method, []).append(\n                start_ev.elapsed_time(end_ev) / num_bench_iters\n            )\n\n        for method in all_benchs:\n            print(\n                f\"{method} = {mean(timings[method]):g}ms (+/- {stdev(timings[method]):g})\"\n            )\n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"scenario\", choices=SCENARIOS.keys())\n    parser.add_argument(\"step\", choices=list(Step), type=Step)\n    parser.add_argument(\"--world-size\", type=int, default=8)\n    parser.add_argument(\"--dtype\", choices=DTYPES.keys(), default=\"bfloat16\")\n    parser.add_argument(\"--num-rounds\", type=int, default=20)\n    parser.add_argument(\"--num-warmup-iters\", type=int, default=5)\n    parser.add_argument(\"--num-bench-iters\", type=int, default=50)\n    parser.add_argument(\"--profile\", action=\"store_true\")\n    args = parser.parse_args()\n\n    conns_from_prev = [None] * args.world_size\n    conns_to_next = [None] * args.world_size\n    for rank in range(args.world_size):\n        end1, end2 = multiprocessing.get_context(\"spawn\").Pipe(duplex=True)\n        conns_to_next[rank] = end1\n        conns_from_prev[(rank + 1) % args.world_size] = end2\n\n    processes = []\n    for rank in range(args.world_size):\n        p = multiprocessing.get_context(\"spawn\").Process(\n            target=run_one_rank,\n            args=(\n                rank,\n                args.world_size,\n                args.scenario,\n                args.step,\n                args.dtype,\n                args.num_rounds,\n                args.num_warmup_iters,\n                args.num_bench_iters,\n                args.profile,\n                conns_from_prev[rank],\n                conns_to_next[rank],\n            ),\n            daemon=True,\n        )\n        p.start()\n        processes.append(p)\n\n    print(\"LAUNCHED\")\n\n    for rank, p in enumerate(processes):\n        p.join()\n        print(f\"Rank {rank} exited with {p.exitcode}\")\n\n    print(\"JOINED\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_sp24.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Tuple\n\nimport torch\nimport torch.nn.functional as F\n\nimport xformers.ops as xops\nfrom torch import nn\nfrom utils import benchmark_main_helper2, DTYPE2STR, product_dict\n\nmin_run_time = 0.5\ndevice = torch.device(\"cuda\")\n\nCASES = list(\n    product_dict(\n        B_in_hidden_out_ft=[\n            (2048 * 8, 2048, 2048 * 3, 2048),\n            (2048, 5120, 5120 * 3, 5120),  # 13b\n            (1024, 8192, 8192 * 3, 8192),  # 30b\n            (2048, 8192, 8192 * 3, 8192),  # 30b\n            (2048 * 2, 8192, 8192 * 3, 8192),  # 30b\n            # DINO ViT-L: lg + sm crops (patch16)\n            (64 * 2 * (14 * 14 + 1) + 64 * 8 * (6 * 6 + 1), 1024, 1024 * 4, 1024),\n            # DINO ViT-g: lg + sm crops (patch16)\n            (\n                12 * 2 * (16 * 16 + 1 + 11) + 12 * 8 * (7 * 7 + 1 + 11),\n                1536,\n                1536 * 4,\n                1536,\n            ),\n        ],\n        dtype=[torch.half],\n        bias=[False],\n    )\n)\n\n\nclass Mlp(nn.Module):\n    LINEAR_CLS = nn.Linear\n\n    def __init__(\n        self, B_in_hidden_out_ft: Tuple[int, int, int, int], dtype, bias: bool, bw: bool\n    ) -> None:\n        B, in_ft, hid_ft, out_ft = B_in_hidden_out_ft\n        super().__init__()\n        self.label = \"mlp\"\n        self.sub_label = (\n            f\"{DTYPE2STR[dtype]} ({B},{in_ft},{hid_ft},{out_ft}){' b' if bias else ''}\"\n        )\n        self.fc1 = self.LINEAR_CLS(in_ft, hid_ft, bias=bias)\n        self.act = nn.GELU()\n        self.fc2 = self.LINEAR_CLS(hid_ft, out_ft, bias=bias)\n        self.grad = torch.randn([B, out_ft], device=\"cuda\", dtype=dtype)\n        self.input = torch.randn(\n            [B, in_ft], device=\"cuda\", dtype=dtype, requires_grad=True\n        )\n        self.out = self.input\n        self.to(\"cuda\").to(dtype)\n\n    def fw(self):\n        x = self.input\n        x = self.fc1(x)\n        x = self.act(x)\n        x = self.fc2(x)\n        self.out = x\n\n    def bw(self):\n        self.out.backward(self.grad, retain_graph=True)\n\n\nclass MlpDenseMask(Mlp):\n    def fw(self):\n        x = self.input\n        x = self.fc1(x)\n\n        mask = torch.ops.xformers.sparse24_largest_mask_2d(x)\n        x = mask * x\n\n        x = self.act(x)\n        x = self.fc2(x)\n        self.out = x\n\n\nclass MlpAct24(Mlp):\n    def fw(self):\n        x = self.input\n        x = self.fc1(x)\n\n        x = xops.sparsify24(x)\n\n        x = self.act(x)\n        x = self.fc2(x)\n        self.out = x\n\n\nclass LinearW24(torch.nn.Linear):\n    def forward(self, input: torch.Tensor) -> torch.Tensor:\n        w_sparse = xops.sparsify24(\n            self.weight,\n            gradient=\"24dense\",\n            backend=\"cusparselt\",\n        )\n        return F.linear(input, w_sparse, self.bias)\n\n\nclass MlpW24(Mlp):\n    LINEAR_CLS = LinearW24\n\n\nclass MicrobenchmarkBase:\n    def __init__(\n        self, B_in_hidden_out_ft: Tuple[int, int, int, int], dtype, bias: bool, bw: bool\n    ) -> None:\n        B, in_ft, hid_ft, out_ft = B_in_hidden_out_ft\n        super().__init__()\n        self.label = \"mlp\"\n        self.sub_label = (\n            f\"{DTYPE2STR[dtype]} ({B},{in_ft},{hid_ft},{out_ft}){' b' if bias else ''}\"\n        )\n        self.input = torch.randn(\n            [B, in_ft], device=\"cuda\", dtype=dtype, requires_grad=True\n        )\n        self.input_colMajor = self.input.t().contiguous().t()\n        self.input_sp = xops.sparsify24(self.input)\n\n    def bw(self) -> None:\n        return None\n\n\nclass MicrobenchmarkSparsify24(MicrobenchmarkBase):\n    def fw(self) -> torch.Tensor:\n        xops.sparsify24(self.input)\n        return self.input\n\n\nclass MicrobenchmarkSp24ApplyDense(MicrobenchmarkBase):\n    def fw(self) -> torch.Tensor:\n        xops.sparsify24_like(self.input, pattern=self.input_sp, out_dense=True)\n        return self.input\n\n\nclass MicrobenchmarkSp24ApplyDenseT(MicrobenchmarkBase):\n    def fw(self) -> torch.Tensor:\n        xops.sparsify24_like(self.input_colMajor, pattern=self.input_sp, out_dense=True)\n        return self.input\n\n\nclass MicrobenchmarkInputClone(MicrobenchmarkBase):\n    def fw(self) -> torch.Tensor:\n        self.input.clone()\n        return self.input\n\n\nfunctions = {\n    \"act24\": MlpAct24,\n    \"dense\": Mlp,\n    \"w24\": MlpW24,\n    \"s24_inp_sparsify24\": MicrobenchmarkSparsify24,\n    \"s24_inp_apply_dense\": MicrobenchmarkSp24ApplyDense,\n    \"s24_inp_apply_dense_t\": MicrobenchmarkSp24ApplyDenseT,\n    \"s24_inp_clone\": MicrobenchmarkInputClone,\n}\nbenchmark_main_helper2(\n    \"sp24_fw\", fw=True, cases=CASES, functions=functions, min_run_time=min_run_time\n)\nbenchmark_main_helper2(\n    \"sp24_fwbw\",\n    fw=True,\n    bw=True,\n    cases=CASES,\n    functions=functions,\n    min_run_time=min_run_time,\n)\n"
  },
  {
    "path": "xformers/benchmarks/benchmark_tiled_matmul.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport itertools\n\nimport torch\nfrom torch.utils import benchmark\n\nfrom xformers.benchmarks.utils import benchmark_main_helper, DTYPE2STR\nfrom xformers.ops.tiled_matmul import tiled_matmul\n\nmin_run_time = 5\n\n\nSHAPES = {\n    \"llama1_65b_mha_fwd\": ([16384], [1024] * 3, [8192]),\n    \"llama1_65b_mha_bwd_input\": ([16384], [8192], [1024] * 3),\n    \"llama1_65b_mha_bwd_weight\": ([8192], [1024] * 3, [16384]),\n    \"llama1_65b_ffn_fwd\": ([16384], [2752] * 2, [8192]),\n    \"llama1_65b_ffn_bwd_input\": ([16384], [8192], [2752] * 2),\n    \"llama1_65b_ffn_bwd_weight\": ([8192], [2752] * 2, [16384]),\n    \"llama2_150b_mha_fwd\": ([16384], [1536, 128, 128], [12288]),\n    \"llama2_150b_mha_bwd_input\": ([16384], [12288], [1536, 128, 128]),\n    \"llama2_150b_mha_bwd_weight\": ([12288], [1536, 128, 128], [16384]),\n    \"llama2_150b_ffn_fwd\": ([16384], [4096] * 2, [12288]),\n    \"llama2_150b_ffn_bwd_input\": ([16384], [12288], [4096] * 2),\n    \"llama2_150b_ffn_bwd_weight\": ([12288], [4096] * 2, [16384]),\n}\n\n\ndef product_dict(**kwargs):\n    keys = kwargs.keys()\n    vals = kwargs.values()\n    for instance in itertools.product(*vals):\n        yield dict(zip(keys, instance))\n\n\nCASES = list(\n    product_dict(\n        shape_name=SHAPES.keys(),\n        dtype=[\n            # torch.float32,\n            torch.bfloat16,\n            # torch.float16,\n        ],\n    )\n)\n\n\ndef matmul_per_tile(a, b):\n    c = []\n    for n in range(len(a)):\n        c.append([])\n        for m in range(len(b[0])):\n            c[-1].append(\n                sum([torch.matmul(a[n][k], b[k][m]) for k in range(len(a[0]))])\n            )\n    return c\n\n\ndef benchmark_tiled_matmul(shape_name, dtype):\n    ms, ns, ks = SHAPES[shape_name]\n    m, n, k = sum(ms), sum(ns), sum(ks)\n\n    a = torch.randn((m, k), device=\"cuda\", dtype=dtype)\n    b = torch.randn((k, n), device=\"cuda\", dtype=dtype)\n\n    a_tiles = [[y.clone() for y in x.split(ks, dim=1)] for x in a.split(ms, dim=0)]\n    b_tiles = [[y.clone() for y in x.split(ns, dim=1)] for x in b.split(ks, dim=0)]\n\n    dtype_str = DTYPE2STR.get(dtype, dtype)\n    sub_label = (\n        f\"{dtype_str} {shape_name} \"\n        f\"M={'+'.join(f'{m}' for m in ms)} \"\n        f\"N={'+'.join(f'{n}' for n in ns)} \"\n        f\"K={'+'.join(f'{k}' for k in ks)}\"\n    )\n\n    # Warmup (maybe not needed?)\n    torch.mm(a, b)\n    matmul_per_tile(a_tiles, b_tiles)\n    tiled_matmul(a_tiles, b_tiles)\n\n    yield benchmark.Timer(\n        stmt=\"fn(a, b)\",\n        globals={\n            \"a\": a,\n            \"b\": b,\n            \"fn\": torch.mm,\n        },\n        label=\"tiled_matmul\",\n        description=\"pytorch_fused\",\n        sub_label=sub_label,\n    )\n    yield benchmark.Timer(\n        stmt=\"fn(a, b)\",\n        globals={\n            \"a\": a_tiles,\n            \"b\": b_tiles,\n            \"fn\": matmul_per_tile,\n        },\n        label=\"tiled_matmul\",\n        description=\"pytorch_tiled\",\n        sub_label=sub_label,\n    )\n    yield benchmark.Timer(\n        stmt=\"fn(a, b)\",\n        globals={\n            \"a\": a_tiles,\n            \"b\": b_tiles,\n            \"fn\": tiled_matmul,\n        },\n        label=\"tiled_matmul\",\n        description=\"xformers_tiled\",\n        sub_label=sub_label,\n    )\n\n\nbenchmark_main_helper(benchmark_tiled_matmul, CASES, min_run_time=min_run_time)\n"
  },
  {
    "path": "xformers/benchmarks/readme_benchmark_on_rocm.txt",
    "content": "\n\n    1. #> pip install -e ./\n\n    2. Benchmark for generic fmha inference on ROCM\n\n       #> python xformers/benchmarks/benchmark_mem_eff_attention.py\n\n    3. Benchmark for decoder fmha inference on ROCM\n\n       #> python xformers/benchmarks/benchmark_mem_eff_attn_decoder.py\n\n    4. Other Benchmarks for fmha inference on ROCM\n\n       #> python xformers/benchmarks/benchmark_attn_decoding.py\n       #> python xformers/benchmarks/benchmark_mem_eff_attention_mqa.py\n"
  },
  {
    "path": "xformers/benchmarks/utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport contextlib\nimport copy\nimport csv\nimport functools\nimport glob\nimport itertools\nimport logging\nimport math\nimport os\nimport tempfile\nfrom collections import defaultdict, namedtuple\nfrom dataclasses import replace\nfrom typing import Any, Dict, Generator, Iterator, List, Set, Tuple\n\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\nimport torch\nimport tqdm\nfrom torch.utils import benchmark\n\nsns.set()\n\nTestCase = namedtuple(\"TestCase\", [\"function\", \"name\"])\n\n\nclass NotSupportedInputError(Exception):\n    pass\n\n\n_triton_is_available = torch.cuda.is_available()\nif _triton_is_available:\n    try:\n        import triton\n    except ImportError as e:\n        logging.warning(f\"Triton is not available: {e}.\\nbench_functions\")\n        _triton_is_available = False\n\n\ndef get_func_name(fn):\n    if isinstance(fn, functools.partial):\n        return fn.func.__name__\n    return fn.__name__\n\n\ndef pretty_print(results, title, units) -> None:\n    \"\"\"Printout the contents of a dict as a human-readable and Markdown compatible array\"\"\"\n    print(title)\n    header = \" Units: {:<45}\".format(units)\n    print(\"| \" + header + \"|\" + \"\".join(\"{0:<20}|\".format(k) for k in results.keys()))\n\n    offset = len(header)\n    print(\n        \"|-{}|\".format(\"-\" * offset)\n        + \"\".join(\"{}|\".format(\"-\" * 20) for _ in results.keys())\n    )\n\n    workloads: Dict[str, Any] = {k: [] for v in results.values() for k in v.keys()}\n    for v in results.values():\n        for k in v.keys():\n            workloads[k].append(v[k])\n\n    for k, w in workloads.items():\n        print(\n            \"| {0:<{offset}}|\".format(k, offset=offset)\n            + \"\".join(\"{:<20}|\".format(v) for v in w)\n        )\n\n    print(\"\")\n\n\ndef pretty_plot(\n    results, title, units: str, filename=None, dash_key=\"\", legend_loc=\"lower right\"\n):\n    \"\"\"Graph out the contents of a dict.\n    Dash key means that if the result label has this key, then it will be displayed with a dash\n    \"\"\"\n\n    if not filename:\n        filename = title + \".png\"\n\n    # Sanitize the filename\n    filename = (\n        filename.replace(\" \", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").replace(\":\", \"\")\n    )\n\n    # Gather all the results in \"collumns\"\n    workloads: Dict[str, Any] = {k: [] for v in results.values() for k in v.keys()}\n    for v in results.values():\n        for k in v.keys():\n            workloads[k].append(float(v[k]))\n\n    # Make sure that the plot is big enough\n    f = plt.figure()\n    f.set_figwidth(6)\n    f.set_figheight(6)\n\n    # Display the collections\n    for k, v in workloads.items():\n        if dash_key and dash_key in k:\n            plt.plot(list(results.keys()), v, \"--\")\n        else:\n            plt.plot(list(results.keys()), v)\n\n    plt.title(title)\n    plt.legend(list(workloads.keys()), loc=legend_loc)\n    plt.ylabel(units)\n    plt.xticks(rotation=45)\n\n    plt.savefig(filename, bbox_inches=\"tight\")\n    plt.close(f)\n\n\nif _triton_is_available:\n\n    def bench_functions(\n        test_cases: List[TestCase], shapes, metric_transform, unit, title=\"\"\n    ):\n        device = torch.device(\"cuda\")\n\n        for dtype in [torch.bfloat16, torch.float16, torch.float32]:\n            results: Dict[str, Any] = {}\n\n            for B, M, K in shapes:\n                a = torch.rand(B, M, K, device=device, dtype=dtype, requires_grad=True)\n\n                for testcase in test_cases:\n                    time = triton.testing.do_bench(lambda: testcase.function(a))[0]\n\n                    metric = metric_transform(a, time)\n\n                    key = f\"B={B}, M={M}, K={K}\"\n                    if key not in results:\n                        results[key] = {}\n\n                    results[key][testcase.name] = f\"{metric:.1f}\"\n\n            pretty_print(\n                results,\n                title=\" ------------- Type: {} ------------- \".format(dtype),\n                units=unit,\n            )\n            pretty_plot(results, title + str(dtype), unit, dash_key=\"pytorch\")\n\n\ndef pretty_barplot(results, title, units: str, filename=None, dash_key=\"\"):\n    \"\"\"Graph out the contents of a dict.\n    Dash key means that if the result label has this key, then it will be displayed with a dash\n    \"\"\"\n\n    if not filename:\n        filename = title + \".png\"\n\n    # Sanitize the filename\n    filename = (\n        filename.replace(\" \", \"_\").replace(\"/\", \"_\").replace(\"-\", \"_\").replace(\":\", \"\")\n    )\n\n    xlabels = list(results.keys())\n    # Gather all the results in \"collumns\"\n    workloads: Dict[str, Any] = {k: [] for v in results.values() for k in v.keys()}\n    for v in results.values():\n        for k in v.keys():\n            workloads[k].append(float(v[k]))\n\n    options = list(workloads.keys())\n    group_len = len(options)\n    for key in workloads.keys():\n        num_groups = len(workloads[key])\n        break\n    group_width = group_len + 1\n\n    # Make sure that the plot is big enough\n    f = plt.figure()\n    f.set_figwidth(6)\n    f.set_figheight(6)\n\n    for idx in range(group_len):\n        option = options[idx]\n        values = workloads[option]\n        xloc = np.arange(1 + idx, group_width * num_groups, group_width)\n        plt.bar(xloc, values, width=1, edgecolor=\"black\")\n\n    plt.title(title)\n    plt.legend(list(workloads.keys()), loc=\"upper right\")\n    plt.ylabel(units)\n\n    ax = plt.gca()\n    xticks_loc = np.arange(\n        1 + (group_len - 1) / 2.0, group_width * num_groups, group_width\n    )\n    ax.set_xticks(xticks_loc, xlabels)\n    plt.xticks(rotation=45)\n\n    plt.setp(ax.xaxis.get_majorticklabels(), ha=\"right\")\n    ax.set_axisbelow(True)\n    ax.yaxis.grid(color=\"gray\", linestyle=\"dashed\")\n    ax.xaxis.grid(color=\"gray\", linestyle=\"dashed\")\n\n    plt.savefig(filename, bbox_inches=\"tight\")\n    plt.close(f)\n\n\ndef rmf(filename: str) -> None:\n    \"\"\"Remove a file like rm -f.\"\"\"\n    try:\n        os.remove(filename)\n    except FileNotFoundError:\n        pass\n\n\n@contextlib.contextmanager\ndef temp_files_ctx(num: int) -> Generator:\n    \"\"\"A context to get tempfiles and ensure they are cleaned up.\"\"\"\n    files = [tempfile.mkstemp()[1] for _ in range(num)]\n\n    yield tuple(files)\n\n    # temp files could have been removed, so we use rmf.\n    for name in files:\n        rmf(name)\n\n\nMETA_ALGORITHM = \"algorithm\"\nBASELINE_DESCRIPTIONS = [\"eager\", \"vanilla\", \"pytorch\"]\n\n\n# Serialize/unserialize to CSV\n# We could use pkl, but resort to CSV for readability\ndef _benchmark_results_from_csv(filename: str) -> List[Tuple[Dict[str, Any], Any]]:\n    parts = os.path.basename(filename).split(\".\")\n    env = \"\"\n    description = \"\"\n    if len(parts) == 3:\n        env = parts[1]\n        description = parts[0]\n\n    data = []\n    with open(filename, \"r\") as csvfile:\n        reader = csv.DictReader(csvfile)\n        for row in reader:\n            if description != \"\" and row[\"description\"] not in BASELINE_DESCRIPTIONS:\n                row[\"description\"] = description\n            task_spec = benchmark.utils.common.TaskSpec(\n                stmt=\"\",\n                setup=\"\",\n                global_setup=\"\",\n                label=row[\"label\"],\n                sub_label=row[\"sub_label\"],\n                description=row[\"description\"],\n                env=env,\n                num_threads=int(row[\"num_threads\"]),\n            )\n            measurement = benchmark.utils.common.Measurement(\n                number_per_run=1,\n                raw_times=[float(row[\"runtime_us\"]) / (1000.0 * 1000)],\n                task_spec=task_spec,\n            )\n            measurement.mem_use = float(row[\"mem_use_mb\"])  # type: ignore\n            data.append(\n                (\n                    {\n                        META_ALGORITHM: (\n                            row[\"algorithm\"] if row[\"algorithm\"] != \"\" else None\n                        ),\n                    },\n                    measurement,\n                )\n            )\n    return data\n\n\ndef _benchmark_results_to_csv(\n    filename: str, results: List[Tuple[Dict[str, Any], Any]]\n) -> None:\n    data = [\n        {\n            \"sub_label\": r.task_spec.sub_label,\n            \"label\": r.task_spec.label,\n            \"num_threads\": r.task_spec.num_threads,\n            \"algorithm\": metadata.get(META_ALGORITHM, \"\"),\n            \"description\": (\n                r.task_spec.description\n                if r.task_spec.description in BASELINE_DESCRIPTIONS\n                else \"\"\n            ),\n            \"runtime_us\": int(1000 * 1000 * r.mean),\n            \"mem_use_mb\": r.mem_use,\n        }\n        for metadata, r in results\n    ]\n    with open(filename, \"w+\", newline=\"\") as csvfile:\n        writer = csv.DictWriter(csvfile, fieldnames=list(data[0].keys()))\n        writer.writeheader()\n        for d in data:\n            writer.writerow(d)\n\n\ndef _finalize_results(results: List[Tuple[Dict[str, Any], Any]]) -> List[Any]:\n    \"\"\"\n    Returns a `benchmark.Compare` object, except that if we have runs\n    with different algorithms, we also add the algorithm name\n    in the column titles\n    \"\"\"\n    all_algorithms: Set[str] = set()\n    all_description: Set[str] = set()\n    for metadata, r in results:\n        algo = metadata.get(META_ALGORITHM, None)\n        if algo is not None:\n            all_algorithms.add(algo)\n        all_description.add(r.task_spec.description)\n    display_algo = len(all_algorithms) > 1\n    display_descr = len(all_description) > 1\n\n    display_results = []\n    for metadata, r in results:\n        algo = metadata.get(META_ALGORITHM, None)\n        if algo is None:\n            display_results.append(r)\n        else:\n            r = copy.copy(r)\n            description = \"\"\n            if display_descr:\n                description = r.task_spec.description\n            if display_algo:\n                if display_descr:\n                    description += \"[\"\n                description += algo\n                if display_descr:\n                    description += \"]\"\n            r.task_spec = replace(r.task_spec, description=description)\n            display_results.append(r)\n    return display_results\n\n\ndef _render_bar_plot(results: List[Any], store_results_folder: str) -> None:\n    if not results:\n        return\n    runtime: Dict[str, Dict[str, float]] = defaultdict(dict)\n    memory_usage: Dict[str, Dict[str, float]] = defaultdict(dict)\n    all_descriptions: List[str] = []\n    for r in results:\n        # Hacky: use a list to preserve order\n        if r.task_spec.description not in all_descriptions:\n            if r.task_spec.description in BASELINE_DESCRIPTIONS:\n                all_descriptions.insert(0, r.task_spec.description)\n            else:\n                all_descriptions.append(r.task_spec.description)\n        runtime[r.task_spec.sub_label][r.task_spec.description] = r.mean\n        memory_usage[r.task_spec.sub_label][r.task_spec.description] = r.mem_use\n    all_data_mem: List[Any] = []\n    all_data_run: List[Any] = []\n    for key, runtime_values in runtime.items():\n        memory_values = memory_usage[key]\n        denom = memory_values.get(all_descriptions[0], math.inf)\n        if denom == 0:\n            all_data_mem.append([key] + [0] * len(all_descriptions))\n        else:\n            all_data_mem.append(\n                [key] + [memory_values.get(d, 0) / denom for d in all_descriptions]\n            )\n        all_data_run.append(\n            [key]\n            + [\n                runtime_values.get(all_descriptions[0], 0)\n                / runtime_values.get(d, math.inf)\n                for d in all_descriptions\n            ]\n        )\n    if all_descriptions[0] == \"\":\n        all_descriptions[0] = \"baseline\"\n    else:\n        all_descriptions[0] = f\"{all_descriptions[0]} (baseline)\"\n\n    for data, filename, title in [\n        (all_data_mem, \"mem.png\", \"Memory usage (vs baseline, lower is better)\"),\n        (\n            all_data_run,\n            \"runtime.png\",\n            \"Runtime speedup (vs baseline, higher is better)\",\n        ),\n    ]:\n        df = pd.DataFrame(data, columns=[\"Configuration\"] + all_descriptions)\n        df.plot(\n            x=\"Configuration\",\n            kind=\"bar\",\n            stacked=False,\n            title=title,\n        )\n        plt.tight_layout()\n        filename_full = os.path.join(store_results_folder, filename)\n        plt.savefig(filename_full)\n        print(f\"Saved plot: {filename_full}\")\n\n\ndef create_argparser() -> argparse.ArgumentParser:\n    \"\"\"\n    Create CLI argument parser.\n    \"\"\"\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\n        \"--fn\", default=None, type=str, help=\"Only benchmark this function\"\n    )\n    parser.add_argument(\n        \"--label\", default=None, type=str, help=\"Store results to a file\"\n    )\n    parser.add_argument(\n        \"--fail_if_regression\",\n        action=\"store_true\",\n        help=\"Enabled in CI to check against performance regressions\",\n    )\n    parser.add_argument(\n        \"--compare\",\n        default=None,\n        type=str,\n        help=\"Compare to previously stored benchmarks (coma separated)\",\n    )\n    parser.add_argument(\n        \"--omit-baselines\",\n        action=\"store_true\",\n        help=\"Do not run the (potentially slow) baselines\",\n    )\n    parser.add_argument(\n        \"--quiet\",\n        action=\"store_true\",\n        help=\"Skip intermediate results and progress bar\",\n    )\n    return parser\n\n\ndef benchmark_main_helper(\n    benchmark_fn, cases: List[Dict[str, Any]], arg_parser=None, **kwargs\n) -> None:\n    \"\"\"\n    Helper function to run benchmarks.\n    Supports loading previous results for comparison, and saving current results to file.\n    \"\"\"\n    arg_parser = arg_parser or create_argparser()\n    args = arg_parser.parse_args()\n\n    if args.fn is not None and args.fn != get_func_name(benchmark_fn):\n        print(f'Skipping benchmark \"{get_func_name(benchmark_fn)}\"')\n        return\n    benchmark_run_and_compare(\n        benchmark_fn=benchmark_fn,\n        cases=cases,\n        optimized_label=\"optimized\" if args.label is None else args.label,\n        fail_if_regression=args.fail_if_regression,\n        compare=args.compare.split(\",\") if args.compare is not None else [],\n        quiet=args.quiet,\n        omit_baselines=args.omit_baselines,\n        **kwargs,\n    )\n\n\ndef benchmark_run_and_compare(\n    benchmark_fn,\n    cases: List[Dict[str, Any]],\n    compare: List[str],\n    omit_baselines: bool = False,\n    fail_if_regression: bool = False,\n    quiet: bool = False,\n    optimized_label: str = \"optimized\",\n    *,\n    min_run_time: float = 2.0,\n    atol_s: float = 30e-6,\n    rtol: float = 0.05,\n) -> None:\n    SKIP_VANILLA_TASKS_IF_ALREADY_DONE = True\n    results_compare_to = []\n    results = []\n\n    store_results_folder = os.path.expanduser(\n        os.path.join(\n            os.environ.get(\n                \"XFORMERS_BENCHMARKS_CACHE\",\n                os.path.join(\"~\", \".cache\", \"xformers\", \"benchmarks\"),\n            ),\n            get_func_name(benchmark_fn),\n        )\n    )\n\n    try:\n        env = (\n            torch.cuda.get_device_name(torch.cuda.current_device())\n            .replace(\" \", \"_\")\n            .replace(\"-\", \"_\")\n            .replace(\".\", \"_\")\n            .replace(\"/\", \"_\")\n        )\n    except (RuntimeError, AssertionError):  # No GPU\n        env = \"cpu\"\n    assert (\n        \".\" not in optimized_label\n    ), f\"label=`{optimized_label}` should not contain dots\"\n    assert \".\" not in env, f\"env=`{env}` should not contain dots\"\n\n    os.makedirs(store_results_folder, exist_ok=True)\n\n    # Load runs that we want to compare to\n    skip_vanilla_tasks = set()\n    for cmp_name in compare:\n        name_with_env = cmp_name if \".\" in cmp_name else f\"{cmp_name}.*\"\n        for filename in glob.glob(\n            os.path.join(store_results_folder, f\"{name_with_env}.csv\")\n        ):\n            loaded = _benchmark_results_from_csv(filename)\n            for m, r in loaded:\n                if m.get(META_ALGORITHM) is not None:\n                    m[META_ALGORITHM] = m[META_ALGORITHM].partition(\"@\")[0]\n                if r.task_spec.env == env and SKIP_VANILLA_TASKS_IF_ALREADY_DONE:\n                    skip_vanilla_tasks.add(\n                        (r.task_spec.sub_label, r.task_spec.num_threads)\n                    )\n            results_compare_to += loaded\n\n    if not quiet:\n        pbar = tqdm.tqdm(cases, leave=False)\n        cases = pbar\n    for case in cases:\n        if quiet:\n            print(str(case))\n        else:\n            pbar.write(f\"====== {str(case)} ======\")\n        try:\n            benchmarks_generator = benchmark_fn(**case)\n        except NotImplementedError:\n            # pbar.write(f\"Skipped (NotImplementedError)\")\n            continue\n        except RuntimeError as e:\n            if not _is_oom_error(e):\n                raise\n            if not quiet:\n                pbar.write(\"Skipped (OOM)\")\n            continue\n\n        name = None\n        try:\n            torch.cuda.synchronize()\n            torch.cuda.reset_peak_memory_stats()\n            mem_begin = torch.cuda.max_memory_allocated() / 2**20\n\n            for benchmark_object in benchmarks_generator:\n                memory = math.inf\n                try:\n\n                    is_optimized = (\n                        benchmark_object._task_spec.description\n                        not in BASELINE_DESCRIPTIONS\n                    )\n                    metadata = {}\n                    if is_optimized:\n                        metadata[META_ALGORITHM] = (\n                            benchmark_object._task_spec.description\n                        )\n                        benchmark_object._task_spec = replace(\n                            benchmark_object._task_spec, description=optimized_label\n                        )\n                    elif (\n                        omit_baselines\n                        or (\n                            benchmark_object._task_spec.sub_label,\n                            benchmark_object._task_spec.num_threads,\n                        )\n                        in skip_vanilla_tasks\n                    ):\n                        continue\n\n                    torch.cuda.synchronize()\n                    benchmark_object._task_spec = replace(\n                        benchmark_object._task_spec, env=env\n                    )\n                    measurement = benchmark_object.blocked_autorange(\n                        min_run_time=min_run_time\n                    )\n                    torch.cuda.synchronize()\n                    results.append((metadata, measurement))\n                    name = measurement.task_spec.description\n                    memory = torch.cuda.max_memory_allocated() / 2**20 - mem_begin\n                    measurement.mem_use = memory\n\n                    torch.cuda.reset_peak_memory_stats()\n                    mem_begin = torch.cuda.max_memory_allocated() / 2**20\n                except RuntimeError as e:\n                    if not _is_oom_error(e):\n                        raise\n                    if not quiet:\n                        pbar.write(\"Skipped (OOM)\")\n                finally:\n                    del benchmark_object\n                if not quiet:\n                    pbar.write(f\"{name}: memory used: {memory} MB\")\n        except RuntimeError as e:\n            if not _is_oom_error(e):\n                raise\n            if not quiet:\n                pbar.write(\"Skipped (OOM)\")\n        # Display results for benchmarks we just calculated\n        if name is not None and not quiet:\n\n            def matches_current(r):\n                return (\n                    r[1].task_spec.sub_label == results[-1][1].task_spec.sub_label\n                    and r[1].task_spec.label == results[-1][1].task_spec.label\n                )\n\n            pbar.write(\n                str(\n                    benchmark.Compare(\n                        _finalize_results(\n                            list(filter(matches_current, results))\n                            + list(filter(matches_current, results_compare_to))\n                        )\n                    )\n                )\n            )\n\n    results_for_print = _finalize_results(results + results_compare_to)\n    benchmark.Compare(results_for_print).print()\n    _render_bar_plot(results_for_print, store_results_folder)\n\n    # Save runs to a file\n    if results and optimized_label is not None:\n        write_to_path = os.path.join(\n            store_results_folder, f\"{optimized_label}.{env}.csv\"\n        )\n        _benchmark_results_to_csv(write_to_path, results)\n        print(f\"Saved results to {write_to_path}\")\n\n    if fail_if_regression:\n        _fail_if_regressions(\n            results, reference=results_compare_to, atol_s=atol_s, rtol=rtol\n        )\n\n\ndef _is_oom_error(e):\n    return isinstance(\n        e, (torch.cuda.OutOfMemoryError, triton.runtime.autotuner.OutOfResources)\n    )\n\n\ndef _fail_if_regressions(\n    results: List[Any], reference: List[Any], atol_s: float, rtol: float\n) -> None:\n    def get_measurement_id(r):\n        return (\n            r[0].get(META_ALGORITHM, \"\").partition(\"@\")[0],\n            r[1].task_spec.label,\n            r[1].task_spec.sub_label,\n            r[1].task_spec.env,\n        )\n\n    id_to_result = {}\n    for r in results:\n        id_to_result[get_measurement_id(r)] = r[1]\n\n    num_better = 0\n    num_worse = 0\n    num_nochange = 0\n    num_unk = 0\n    reference_set = set()\n    for ref in reference:\n        if ref[1].task_spec.description in BASELINE_DESCRIPTIONS:\n            continue\n        benchmark_id = get_measurement_id(ref)\n        if benchmark_id in reference_set:\n            raise ValueError(f\"Duplicate benchmark in reference for {benchmark_id}\")\n        reference_set.add(benchmark_id)\n        if benchmark_id not in id_to_result:\n            num_unk += 1\n            continue\n        res = id_to_result[benchmark_id]\n        # If significative change\n        if abs(ref[1].mean - res.mean) - rtol * ref[1].mean > atol_s:\n            is_now_better = res.mean < ref[1].mean\n            if is_now_better:\n                num_better += 1\n            else:\n                num_worse += 1\n            cmp = \"IMPROVED\" if is_now_better else \"REGRESS \"\n            print(cmp, benchmark_id, f\"ref={ref[1].mean}\", f\"now={res.mean}\")\n        else:\n            num_nochange += 1\n\n    print(\"Regression test summary:\")\n    print(f\"  Better   : {num_better}\")\n    print(f\"  No change: {num_nochange}\")\n    print(f\"  Worse    : {num_worse}\")\n    if num_unk > 0:\n        print(f\"  (no ref) : {num_unk}\")\n    benchmarks_run = num_better + num_nochange + num_worse\n    if num_worse > 1:\n        raise RuntimeError(\"At least one benchmark regressed!\")\n    elif num_unk == benchmarks_run:\n        raise RuntimeError(\"No reference found\")\n    elif benchmarks_run == 0:\n        raise RuntimeError(\"No benchmark was run\")\n\n\ndef benchmark_main_helper2(\n    name: str,\n    functions,\n    fw: bool = False,\n    bw: bool = False,\n    cuda_graph: bool = True,\n    **kwargs,\n) -> None:\n    assert fw or bw\n\n    def handle_case(**case) -> Iterator[benchmark.Timer]:\n        for k, benchmark_cls in functions.items():\n            try:\n                benchmark_object = benchmark_cls(**case, bw=bw)\n            except NotSupportedInputError:\n                continue\n            label = benchmark_object.label\n            label += \"fw\" if fw else \"\"\n            label += \"bw\" if bw else \"\"\n\n            def run_one():\n                if fw:\n                    benchmark_object.fw()\n                if bw:\n                    benchmark_object.bw()\n\n            if cuda_graph:\n                run_one()\n                g = torch.cuda.CUDAGraph()\n                with torch.cuda.graph(g):\n                    run_one()\n\n                def run_one():\n                    g.replay()\n\n            yield benchmark.Timer(\n                stmt=\"fn()\",\n                globals={\n                    \"fn\": run_one,\n                },\n                label=label,\n                description=k,\n                sub_label=benchmark_object.sub_label,\n            )\n\n    handle_case.__name__ = name\n    benchmark_main_helper(handle_case, **kwargs)\n\n\ndef product_dict(**kwargs):\n    keys = kwargs.keys()\n    vals = kwargs.values()\n    for instance in itertools.product(*vals):\n        yield dict(zip(keys, instance))\n\n\nDTYPE2STR = {\n    torch.bfloat16: \"b16\",\n    torch.half: \"f16\",\n    torch.float32: \"f32\",\n}\n"
  },
  {
    "path": "xformers/checkpoint.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport functools\nimport time\nfrom collections import defaultdict\nfrom copy import deepcopy\nfrom dataclasses import astuple, dataclass\nfrom typing import Any, Callable, ContextManager, Dict, List, Optional, Tuple\n\nimport torch\nfrom torch.testing._internal.composite_compliance import (\n    is_inplace,\n    is_inplace_view_fn,\n    is_view_fn,\n)\nfrom torch.utils._python_dispatch import TorchDispatchMode\nfrom torch.utils._pytree import tree_map\n\n_scipy_is_available = False\ntry:\n    from scipy.optimize import Bounds, LinearConstraint, milp\n\n    _scipy_is_available = True\nexcept ImportError:\n    _scipy_is_available = False\n\n\ntry:\n    # let's keep BC for older PyTorch for now\n    from torch.distributed.algorithms._checkpoint.checkpoint_wrapper import (\n        ActivationWrapper,\n    )\n    from torch.utils.checkpoint import (  # type: ignore\n        _CachedTorchDispatchMode,\n        _CachingTorchDispatchMode,\n    )\nexcept ImportError:\n    ActivationWrapper = torch.nn.Module  # type: ignore\n\n    class _NotAvailable:\n        def __init__(self, *args, **kwargs):\n            raise RuntimeError(\"Need PyTorch >= 2.2\")\n\n    _CachedTorchDispatchMode = _NotAvailable  # type: ignore\n    _CachingTorchDispatchMode = _NotAvailable  # type: ignore\n\n\ntry:\n    from torch.utils.checkpoint import SAC_IGNORED_OPS as _ignored_ops  # type: ignore\n\n    _PT_HAS_NEW_IMPL = True\nexcept ImportError:\n    from torch.utils.checkpoint import _ignored_ops  # type: ignore\n\n    _PT_HAS_NEW_IMPL = False\n\n\n_additional_ignored_ops = {\n    torch.ops.aten.lift_fresh.default,\n    torch.ops.profiler._record_function_exit._RecordFunction,\n    torch.ops.aten.clone.default,  # seems needed for torch.compile\n}\nOPS_TO_ALWAYS_SKIP = _ignored_ops | _additional_ignored_ops\n\n\n@dataclass\nclass ProfileMetadata:\n    name: str\n    time_taken: float\n    memory_used: float\n    curr_idx: int\n    output_ids: Any\n    inplace_info: Tuple[int, int]\n    is_view_like: bool\n    is_rand_op: bool\n\n\ndef _get_default_policy(allow_list=None):\n    _default_allow_list = [\n        \"xformers.efficient_attention_forward_cutlass.default\",\n        \"xformers_flash.flash_fwd.default\",\n        \"aten.addmm.default\",\n        \"aten.mm.default\",\n    ]\n    if allow_list is None:\n        allow_list = _default_allow_list\n\n    def _default_policy(ctx, func, *args, **kwargs):\n        return str(func) in allow_list\n\n    return _default_policy\n\n\nclass VerboseTorchDispatchMode(TorchDispatchMode):\n    def __init__(self):\n        self.operators = []\n\n    def __torch_dispatch__(self, func, types, args=(), kwargs=None):\n        if kwargs is None:\n            kwargs = {}\n        self.operators.append(func)\n        return func(*args, **kwargs)\n\n\ndef list_operators(function, *args, **kwargs):\n    \"\"\"\n    Returns the list of operators used inside `function` with\n    *args and **kwargs\n    \"\"\"\n    verbose_mode = VerboseTorchDispatchMode()\n    with verbose_mode:\n        function(*args, **kwargs)\n    return verbose_mode.operators\n\n\nclass CachedTorchDispatchMode(_CachedTorchDispatchMode):\n    def __init__(self, policy_fn, storage, allow_cache_entry_mutation):\n        global _PT_HAS_NEW_IMPL\n        if _PT_HAS_NEW_IMPL:\n            super().__init__(policy_fn, storage, allow_cache_entry_mutation)\n        else:\n            super().__init__(policy_fn, storage)\n\n    # this is here for the old implementations\n    def pop_from_storage(self, func, args, kwargs):\n        # the autograd engine might add spurious views. This is a basic\n        # guard and should be improved\n        if self.storage[func]:\n            return self.storage[func].pop(0)\n        return func(*args, **kwargs)\n\n\nclass NullTorchDispatchMode(TorchDispatchMode):\n    def __torch_dispatch__(self, func, types, args=(), kwargs=None):\n        if kwargs is None:\n            kwargs = {}\n        return func(*args, **kwargs)\n\n\ndef selective_checkpoint_context_fn(policy_fn=None):\n    \"\"\"An activation checkpoint context_fn for selectively deciding what to\n    store and what to recompute. Accepts a custom policy.\n    Args:\n        policy_fn(Union[List[Op], callable]): policy for deciding what to\n            store (instead of recompute). If it's a function, it should\n            be of form (func, *args, **kwargs) -> bool which indicates\n            if func outputs with *args and **kwargs should be stored or not.\n            Additionally, a list[Op] is also supported for easier cases.\n            The op should be in the format `torch.ops.***`, where the `***`\n            names of operators can be obtained with `list_operators`.\n    \"\"\"\n    if policy_fn is None:\n        policy_fn = _get_default_policy()\n    elif isinstance(policy_fn, list):\n        policy_fn = _get_default_policy(policy_fn)\n    else:\n        assert callable(policy_fn), \"policy_fn should be None, list or a callable\"\n\n    temp_storage: Dict[Any, List[Any]] = defaultdict(list)\n    # assumption: grad_mode doesn't change inside function\n    caching_mode: ContextManager[None]\n    if torch.is_grad_enabled():\n        caching_mode = _CachingTorchDispatchMode(deepcopy(policy_fn), temp_storage)\n    else:\n        caching_mode = NullTorchDispatchMode()\n    cached_mode = CachedTorchDispatchMode(deepcopy(policy_fn), temp_storage, True)\n\n    return caching_mode, cached_mode\n\n\ndef checkpoint(\n    function, *args, preserve_rng_state=True, policy_fn=None, **kwargs\n) -> Any:\n    \"\"\"Wrapper around torch.utils.checkpoint that accepts a custom policy\n    function for selectively deciding what to store and what to recompute\n    Args:\n        function: describes what to run in the forward pass of the model or\n            part of the model. It should also know how to handle the inputs\n            passed as the tuple. For example, in LSTM, if user passes\n            ``(activation, hidden)``, :attr:`function` should correctly use the\n            first input as ``activation`` and the second input as ``hidden``\n        preserve_rng_state(bool, optional):  Omit stashing and restoring\n            the RNG state during each checkpoint.\n            Default: ``True``\n        policy_fn(Union[List[Op], callable]): policy for deciding what to\n            store (instead of recompute). If it's a function, it should\n            be of form (func, *args, **kwargs) -> bool which indicates\n            if func outputs with *args and **kwargs should be stored or not.\n            Additionally, a list[Op] is also supported for easier cases.\n            The op should be in the format `torch.ops.***`, where the `***`\n            names of operators can be obtained with `list_operators`.\n        *args: Arguments to pass in to the given ``function``.\n        **kwargs: Keyword arguments to pass into the given ``function``.\n    \"\"\"\n    return torch.utils.checkpoint.checkpoint(\n        function,\n        *args,\n        use_reentrant=False,\n        preserve_rng_state=preserve_rng_state,\n        context_fn=functools.partial(selective_checkpoint_context_fn, policy_fn),\n        **kwargs,\n    )\n\n\nclass ProfileOperatorsTorchDispatchMode(TorchDispatchMode):\n    def __init__(self, num_runs: int = 10) -> None:\n        self.data: List[ProfileMetadata] = []\n        self.num_runs: int = num_runs\n\n    def _get_inplace_metadata(self, func, out) -> Tuple[int, int, Tuple[int, ...]]:\n        curr_idx = len(self.data)\n\n        def get_tensor_id(e):\n            return (\n                e.untyped_storage().data_ptr() if isinstance(e, torch.Tensor) else None\n            )\n\n        output_ids = tree_map(get_tensor_id, out)\n        if not is_inplace(func):\n            return curr_idx, output_ids, ()\n\n        op_id = curr_idx\n        op_parent_id = -1\n        for i, d in enumerate(self.data):\n            # find the first occurence of a tensor that\n            # shares the same storage as the current tensor\n            past_output_ids = d.output_ids\n            past_output_ids = (\n                [past_output_ids]\n                if not isinstance(past_output_ids, (list, tuple, dict))\n                else past_output_ids\n            )\n            if output_ids in past_output_ids:\n                op_parent_id = i\n                break\n        if op_parent_id < 0:\n            op_parent_id = op_id\n        inplace_info = (op_id, op_parent_id)\n        return curr_idx, output_ids, inplace_info\n\n    def __torch_dispatch__(self, func, types, args=(), kwargs=None):\n        if kwargs is None:\n            kwargs = {}\n        out = func(*args, **kwargs)\n\n        curr_idx, output_ids, inplace_info = self._get_inplace_metadata(func, out)\n        is_view_like = is_view_fn(func) or is_inplace_view_fn(func)\n        is_rand_op = torch.Tag.nondeterministic_seeded in func.tags\n        # sdpa has non-deterministic seed, but might be deterministic\n        # if no dropout is applied\n        if func.overloadpacket.__name__ == \"_scaled_dot_product_flash_attention\":\n            is_rand_op = kwargs.get(\"dropout_p\", 0) != 0\n\n        # get runtime info of func\n        torch.cuda.synchronize()\n        t = time.time()\n        for i in range(self.num_runs):\n            func(*args, **kwargs)\n        torch.cuda.synchronize()\n        time_taken = (time.time() - t) / self.num_runs\n\n        # get memory usage of func\n        torch.cuda.reset_peak_memory_stats()\n        mem1 = torch.cuda.max_memory_allocated() / 2**20\n        func(*args, **kwargs)\n        mem2 = torch.cuda.max_memory_allocated() / 2**20\n\n        self.data.append(\n            ProfileMetadata(\n                func,\n                time_taken,\n                mem2 - mem1,\n                curr_idx,\n                output_ids,\n                inplace_info,\n                is_view_like,\n                is_rand_op,\n            )\n        )\n        return out\n\n\ndef _analyze_operators(function, *args) -> List[ProfileMetadata]:\n    \"\"\"\n    Use ProfileOperatorsTorchDispatchMode to get runtime and memory info.\n\n    Args:\n        function: The function to optimize which will be selectively checkpointed. Usually the forward pass\n            of the model.\n        *args: Arguments to pass in to the given ``function``.\n\n    Returns:\n        A list of tuples, where each tuples contains the name of the operator, the runtime of the operator,\n            and the memory usage of the operator.\n\n    \"\"\"\n    profile_ops = ProfileOperatorsTorchDispatchMode()\n    with profile_ops:\n        function(*args)\n\n    data = profile_ops.data\n    return data\n\n\ndef get_optimal_checkpoint_policy(function, *args, memory_budget: float) -> Callable:\n    \"\"\"\n    Given a function, its arguments, and the maximum amount of memory available,\n    find the subset of operators that can be optimized to reduce runtime while still fitting within the memory budget.\n\n    Args:\n        function: The function to optimize which will be selectively checkpointed. Usually the forward pass\n            of the model.\n        *args: Arguments to pass in to the given ``function``.\n        memory_budget (float): A float between 0 and 1 which describes what percentage of the total memory to use.\n\n    Returns:\n        A callable policy which can be passed to xformers.checkpoint()\n\n    Raises:\n        RuntimeError: If `scipy` is not available.\n        ValueError: If `memory_budget` is not a float between 0 and 1.\n\n    \"\"\"\n    if not _scipy_is_available:\n        raise RuntimeError(\n            \"Please install scipy 1.9.0+ to use `get_optimal_checkpoint_policy`. You can do so using \"\n            \"`pip install scipy`.\"\n        )\n    if memory_budget < 0 or memory_budget > 1:\n        raise ValueError(\n            f\"`memory_budget` must be a float between 0 and 1. Got {memory_budget}.\"\n        )\n\n    data = _analyze_operators(function, *args)\n    # remove aten.detach.default from the list of ops because autograd\n    # inserts those during backward and it breaks the fwd-bwd alignment\n    data = [x for x in data if x.name not in OPS_TO_ALWAYS_SKIP]\n\n    ops, runtimes_, memory_, new_ids, _, inplace_ops_, view_like_ops_, rand_ops_ = zip(\n        *[astuple(x) for x in data]\n    )\n    runtimes = torch.tensor(runtimes_, dtype=torch.float64)\n    memory = torch.tensor(memory_, dtype=torch.float64)\n    view_like_ops = [i for i, x in enumerate(view_like_ops_) if x]\n    rand_ops = [i for i, x in enumerate(rand_ops_) if x]\n\n    # remap the inplace indices as we have removed OPS_TO_ALWAYS_SKIP\n    inplace_ops = [tuple(map(new_ids.index, x)) for x in inplace_ops_ if x]\n\n    # the last operation is always stored as the output of the checkpoint\n    # block, so we can avoid recomputing it. We set the memory to zero\n    # instead of adding a new constraint because we want both the 0 and 1\n    # endpoints for memory_budget to be valid\n    # FIXME: this heuristic for finding the last non-view non-inplace op\n    # might not always be correct, which would yield suboptimal policies\n    last_op = len(ops) - 1\n    skip_ops_ = set(view_like_ops) | set([x[0] for x in inplace_ops])\n    skip_ops = sorted(list(skip_ops_))\n    for op in reversed(skip_ops):\n        if op == last_op:\n            last_op -= 1\n\n    memory[last_op] = 0\n\n    max_memory = memory_budget * memory.sum().item()\n\n    # workaround to fix https://github.com/pytorch/pytorch/issues/121212\n    force_store_random = all([not isinstance(x, torch.Tensor) for x in args])\n\n    optim_output = _optimize_runtime_with_given_memory(\n        memory=memory,\n        runtimes=runtimes,\n        max_memory=max_memory,\n        view_like_ops=view_like_ops,\n        inplace_ops=inplace_ops,\n        random_ops=rand_ops,\n        force_store_random=force_store_random,\n    )\n    return _OptimalPolicy(optim_output=optim_output)\n\n\ndef _optimize_runtime_with_given_memory(\n    memory: torch.Tensor,\n    runtimes: torch.Tensor,\n    max_memory: float,\n    view_like_ops: List[int],\n    inplace_ops: List[Tuple[int, ...]],\n    random_ops: List[int],\n    force_store_random: bool,\n) -> torch.Tensor:\n    \"\"\"\n    Given a list of operator names, their corresponding runtimes, and the maximum amount of memory available,\n    find the subset of operators that can be optimized to reduce runtime while still fitting within the memory budget.\n    Uses https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.milp.html\n\n    Args:\n        memory (torch.Tensor): Tensor containing the memory usage of each operator.\n        runtimes (torch.Tensor): Tensor containing the runtime of each operator.\n        max_memory (float): Maximum amount of memory to use.\n        view_like_ops ([List[int]): Indices of the view-like ops.\n        inplace_ops (List[Tuple[int, int]]): Tuple with the pair of inplace op -> parent of inplace op.\n            This will be used to add the constraint that in-place ops need to either be\n            stored in memory with the previous op, or recomputed with the previous op.\n        random_ops ([List[int]): Indices of the random ops, which will always be recomputed.\n        force_store_random (bool): force random ops to always be stored (instead of recomputed)\n    \"\"\"\n    c = -runtimes  # type: ignore[operator]\n\n    memory_constraint = LinearConstraint(A=memory, ub=max_memory)\n    constraints = [memory_constraint]\n\n    # view-like ops should always be recomputed\n    for i in view_like_ops:\n        A = torch.zeros_like(c)\n        A[i] = 1\n        constraints.append(LinearConstraint(A=A, lb=0, ub=0))\n\n    # inplace ops should always be done in conjunction with its parent op\n    # i.e., if we recompute the parent op the inplace should also be\n    # recomputed, and vice versa\n    for op, op_parent in inplace_ops:\n        A = torch.zeros_like(c)\n        if op != op_parent:\n            A[op_parent] = 1\n            A[op] = -1\n            constraints.append(LinearConstraint(A=A, lb=0, ub=0))\n        else:\n            # if op == op_parent, it's because it's the first op\n            # that is inplace. Thus never recompute it\n            A[op] = 1\n            constraints.append(LinearConstraint(A=A, lb=1, ub=1))\n\n    # ideally, always recompute random ops\n    # in practice, due to a bug in https://github.com/pytorch/pytorch/issues/121212\n    # sometimes we need to store them to avoid correctness issues\n    for i in random_ops:\n        A = torch.zeros_like(c)\n        A[i] = 1\n        val = int(force_store_random)\n        constraints.append(LinearConstraint(A=A, lb=val, ub=val))\n\n    integrality = torch.ones_like(c)\n    res = milp(\n        c=c, constraints=constraints, integrality=integrality, bounds=Bounds(0, 1)\n    )\n    if not res.success:\n        raise ValueError(\n            \"The problem is infeasible, and probably due to a change in xformers \"\n            \"that makes random ops always be stored. Try passing a larger memory_budget. \"\n            \"This will be fixed once https://github.com/pytorch/pytorch/issues/121212 \"\n            \"is solved\"\n        )\n    x = torch.from_numpy(res.x)\n    return x\n\n\nclass _OptimalPolicy:\n    def __init__(self, optim_output: torch.Tensor):\n        self.counter = 0\n        self.optim_output = optim_output.tolist()\n\n    def __call__(self, ctx, func, *args, **kwargs) -> bool:\n        # returning False means recompute, True means store in memory\n        if func in OPS_TO_ALWAYS_SKIP:\n            return False\n        count = self.counter\n        self.counter += 1\n        return self.optim_output[count] == 1\n\n\nclass SelectiveCheckpointWrapper(ActivationWrapper):\n    def __init__(self, mod, memory_budget=None, policy_fn=None):\n        super().__init__(mod)\n        if not ((memory_budget is None) ^ (policy_fn is None)):\n            raise ValueError(\"Need to specify either policy_fn or memory_budget\")\n        self.memory_budget = memory_budget\n        self.policy_fn = policy_fn\n\n        try:\n            # for backward-compatibility as this doesn't exist in PT anymore\n            torch._dynamo.config._experimental_support_context_fn_in_torch_utils_checkpoint = (\n                True\n            )\n        except AttributeError:\n            pass\n\n    @torch.compiler.disable\n    def _get_policy_fn(self, *args, **kwargs):\n        if not torch.is_grad_enabled():\n            # no need to compute a policy as it won't be used\n            return []\n        # if policy is not specified, initialize policy for a given memory budget\n        with torch.random.fork_rng():\n            policy_fn = get_optimal_checkpoint_policy(\n                self._checkpoint_wrapped_module,\n                *args,\n                **kwargs,\n                memory_budget=self.memory_budget,\n            )\n        if (\n            torch.distributed.is_available()\n            and torch.distributed.is_initialized()\n            and torch.distributed.get_world_size() > 1\n        ):\n            # use the same policy across different GPUs\n            objects = [policy_fn]\n            torch.distributed.broadcast_object_list(objects, src=0)\n            policy_fn = objects[0]\n        return policy_fn\n\n    def get_policy_fn(self, *args, **kwargs):\n        if self.policy_fn is None:\n            self.policy_fn = self._get_policy_fn(*args, **kwargs)\n        return self.policy_fn\n\n    def forward(self, *args, **kwargs):\n        policy_fn = self.get_policy_fn(*args, **kwargs)\n        return checkpoint(\n            self._checkpoint_wrapped_module, *args, **kwargs, policy_fn=policy_fn\n        )\n\n\ndef selective_checkpoint_wrapper(\n    module: torch.nn.Module,\n    memory_budget: Optional[float] = None,\n    policy_fn: Optional[Callable] = None,\n):\n    \"\"\"\n    Wrap a module with selective activation checkpointing.\n\n    It behaves similarly to PyTorch's checkpoint_wrapper, but gives the possibility\n    to the user to either specify a handcrafted policy_fn, or to let an optimization\n    algorithm to select the policy given a user-specified memory_budget.\n\n    The user should either specify the memory_budget argument or the policy_fn.\n\n    The memory_budget is a float value between 0 (recompute everything in the backward) or 1\n    (store everything for backward). Using a value of 0 should be similar to PyTorch's\n    activation checkpoint, while 1 should be similar to the behavior of not using any\n    activation checkpointing.\n    \"\"\"\n    return SelectiveCheckpointWrapper(module, memory_budget, policy_fn)\n"
  },
  {
    "path": "xformers/components/attention/attention_patterns.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport math\nfrom typing import List\n\nimport numpy as np\nimport torch\n\n\n# generic nd cases\ndef _generate_nd_grid(*sizes):\n    coords = [torch.arange(s) for s in sizes]\n    return torch.meshgrid(*coords)\n\n\ndef local_nd_distance(*sizes, p=2.0, weights=None):\n    if weights is None:\n        weights = (1,) * len(sizes)\n    assert len(sizes) == len(weights)\n    grid = _generate_nd_grid(*sizes)\n    grid = [i.flatten() * w for i, w in zip(grid, weights)]\n    grid = torch.stack(grid, dim=1).float()\n    d = torch.cdist(grid, grid, p=p)\n    return d\n\n\ndef local_nd_gaussian_distribution(*sizes, sigma=1):\n    d = local_nd_distance(*sizes, p=2.0) ** 2\n    d = torch.exp(-0.5 * sigma ** (-2.0) * d)\n    return d\n\n\ndef local_nd_pattern(*sizes, distance, p=2.0):\n    d = local_nd_distance(*sizes, p=p)\n    return d < distance\n\n\ndef axial_nd_pattern(*sizes):\n    # axial is a special case with p=0 and distance=2\n    d = local_nd_distance(*sizes, p=0)\n    return d < 2\n\n\ndef random_pattern_from_probability_matrix(dist_matrix, nnz):\n    att = torch.zeros_like(dist_matrix, dtype=torch.bool)\n    # PyTorch multinomial wrongly doesn't support sampling when number of categories\n    # is > 2^24, arguing that it's because it's the max representable consecutive element\n    # in fp32 and that the kernels use float32. This is actually not true, and the kernels\n    # should work fine if double tensor is passed on CPU. This is a bug that was introduced\n    # in https://github.com/pytorch/pytorch/commit/bf04c2ca2f591d98ce57816f0ef0cd20a21bbf66\n    # when unifying the checks between CPU and CUDA. For now, just fall-back to numpy\n    if dist_matrix.numel() > 2**24:\n        dist_matrix = dist_matrix.double()\n        dist_matrix /= dist_matrix.sum()\n        idxs = np.random.choice(\n            dist_matrix.numel(), nnz, p=dist_matrix.flatten(), replace=False\n        )\n        idxs = torch.as_tensor(idxs)\n    else:\n        idxs = torch.multinomial(dist_matrix.flatten(), nnz, replacement=False)\n    att.view(-1)[idxs] = True\n    return att\n\n\ndef global_token_pattern(attention_query_mask: torch.Tensor) -> torch.Tensor:\n    assert attention_query_mask.ndim == 1\n    assert attention_query_mask.dtype == torch.bool\n    attention_query_mask = attention_query_mask[None, :]\n    mask = attention_query_mask | attention_query_mask.transpose(1, 0)\n    return mask\n\n\ndef random_pattern(attn_size: int, sparsity: float) -> torch.Tensor:\n    assert 0 < sparsity < 1\n    mask = torch.rand(attn_size, attn_size) > sparsity\n    return mask\n\n\n# 1d-specific cases\ndef local_1d_pattern(attn_size: int, window_size: int) -> torch.Tensor:\n    assert (\n        window_size % 2 == 1\n    ), \"The window size is assumed to be odd (counts self-attention + 2 wings)\"\n    h_win_size = window_size // 2 + 1\n    return local_nd_pattern(attn_size, distance=h_win_size, p=1.0)\n\n\ndef causal_1d_pattern(attn_size: int) -> torch.Tensor:\n    mask = torch.tril(torch.ones(attn_size, attn_size, dtype=torch.bool))\n    return mask\n\n\n# 2d-specific cases\ndef horizontal_axial_2d_distance(H, W, p=2.0):\n    d = local_nd_distance(H, W, p=p, weights=(1, 0))\n    return d\n\n\ndef vertical_axial_2d_distance(H, W, p=2.0):\n    d = local_nd_distance(H, W, p=p, weights=(0, 1))\n    return d\n\n\ndef local_2d_distance(H, W, p=2.0):\n    return local_nd_distance(H, W, p=p)\n\n\ndef local_2d_gausian_distribution(H, W, sigma=1):\n    return local_nd_gaussian_distribution(H, W, sigma=sigma)\n\n\ndef local_2d_pattern(H, W, distance, p=2.0):\n    return local_nd_pattern(H, W, distance=distance, p=p)\n\n\ndef axial_2d_pattern(H, W):\n    return axial_nd_pattern(H, W)\n\n\ndef swin_attention_pattern(H, W, window_size, shift_size=0):\n    assert H % window_size == 0\n    assert W % window_size == 0\n    assert 0 <= shift_size < window_size, \"shift_size must in 0-window_size\"\n\n    # input grid\n    i, j = _generate_nd_grid(H, W)\n    i, j = i + 0.5, j + 0.5\n\n    # anchors grid\n    # if shift is present, add extra element to the grid\n    # to account for the uneven partitioning\n    extra = int(shift_size % window_size != 0)\n    grid_h = H // window_size + extra\n    grid_w = W // window_size + extra\n\n    ii, jj = _generate_nd_grid(grid_h, grid_w)\n    # convert shift to be compatible with the paper representation\n    s = (-shift_size) % window_size\n    offset = window_size / 2 - s\n    ii = ii * window_size + offset\n    jj = jj * window_size + offset\n\n    input_coords = torch.stack([i.flatten(), j.flatten()], 1).float()\n    anchors_coords = torch.stack([ii.flatten(), jj.flatten()], 1).float()\n\n    anchor_id = torch.cdist(input_coords, anchors_coords, p=2).argmin(1)\n    mask = anchor_id[:, None] == anchor_id[None, :]\n    return mask\n\n\ndef dilated_2d_pattern(H, W, k=2):\n    \"\"\"\n    Returns a 2d pattern that samples 1 every k elements in the attention mask.\n    Can be seen as a form of downsampling, where every pixel attends to a downsampled\n    version of the input.\n    \"\"\"\n    d_h = local_nd_distance(H, W, p=1, weights=(1, 0))\n    d_w = local_nd_distance(H, W, p=1, weights=(0, 1))\n    d = (d_h.floor() % k == 0) & (d_w.floor() % k == 0)\n    return d\n\n\n# Block sparse utils\ndef block_sparsify_tensor(x, mask, block_size):\n    \"\"\"\n    Block sparsify a tensor, given a mask and block size\n    \"\"\"\n    ret = torch.empty(\n        (x.size(0), mask.sum(), block_size, block_size), dtype=x.dtype, device=x.device\n    )\n\n    for idx, (h, i, j) in enumerate(zip(*mask.nonzero(as_tuple=True))):\n        ret[:, idx, :, :] = x[\n            :,\n            h,\n            i * block_size : (i + 1) * block_size,\n            j * block_size : (j + 1) * block_size,\n        ]\n    return ret\n\n\ndef pattern_to_layout(mask: torch.Tensor, block_size: int) -> torch.Tensor:\n    r\"\"\"\n    Given a mask pattern and blocksize, return the corresponding layout\n    which makes sure that all the positives in the mask are covered\n    \"\"\"\n    assert mask.ndim >= 2, \"We're expecting [Heads, Seq, Seq] or [Seq, Seq]\"\n    _should_squeeze = False\n\n    if mask.ndim == 2:\n        mask = mask.unsqueeze(0)\n        _should_squeeze = True\n\n    assert (\n        mask.shape[1] % block_size == 0 and mask.shape[2] % block_size == 0\n    ), \"We're only handling masks divisible by block_size\"\n\n    # Now mark the mask\n    layout = torch.nn.functional.max_pool2d(\n        mask.to(torch.float), kernel_size=block_size, stride=block_size\n    )\n    layout = layout.to(torch.long)\n\n    if _should_squeeze:\n        layout.squeeze_(0)\n\n    return layout\n\n\ndef alibi_pattern(threshold: float, mask_shape: torch.Size) -> torch.Tensor:\n    r\"\"\"\n    Use the additive bias computation from ALiBi_ to generate a mask.\n    Note that this mask can in turn be used to generate a blocksparse attention computation layout\n\n    .. note: mask_shape is expected to hold the [heads, seq, seq] dimensions\n\n    .. _ALiBi: https://arxiv.org/pdf/2108.12409.pdf\n    \"\"\"\n\n    # CREDITS: code snippet from Ofir Press, one of the authors\n\n    def get_slopes(n: int):\n        def get_slopes_power_of_2(n: int) -> List[float]:\n            start = 2 ** (-(2 ** -(math.log2(n) - 3)))\n            ratio = start\n            return [start * ratio**i for i in range(n)]\n\n        # In the paper, we only train models that have 2^a heads for some a. This function has\n        # some good properties that only occur when the input is a power of 2. To maintain that even\n        # when the number of heads is not a power of 2, we use this workaround.\n        if math.log2(n).is_integer():\n            return get_slopes_power_of_2(n)\n        else:\n            closest_power_of_2 = 2 ** math.floor(math.log2(n))\n            return (\n                get_slopes_power_of_2(closest_power_of_2)\n                + get_slopes(2 * closest_power_of_2)[0::2][: n - closest_power_of_2]\n            )\n\n    maxpos = mask_shape[1]\n    attn_heads = mask_shape[0]\n    slopes = torch.Tensor(get_slopes(attn_heads))\n\n    # In the next line, the part after the * is what constructs the diagonal matrix\n    # (right matrix in Figure 3 in the paper).\n    # If you run it you'll see that it doesn't exactly print out the same matrix as we have in Figure 3,\n    # but one where all rows are identical.\n    # This works because the softmax operation is invariant to translation,\n    # and our bias functions are always linear.\n    alibi = slopes.unsqueeze(1).unsqueeze(1) * torch.arange(maxpos).unsqueeze(\n        0\n    ).unsqueeze(0).expand(attn_heads, -1, -1)\n    alibi = alibi.view(attn_heads, 1, maxpos)\n\n    # Now threshold arbitrarily, report the mask\n    return alibi < threshold\n\n\ndef layout_to_pattern(layout: torch.Tensor, block_size: int):\n    r\"\"\"\n    create a pattern of shape [heads, seq, seq] out of a blocksparse\n    layout of shape [heads, seq/block_size, seq/block_size]\n    \"\"\"\n    return torch.kron(layout, torch.ones(block_size, block_size))\n"
  },
  {
    "path": "xformers/csrc/attention/attention.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <torch/csrc/stable/library.h>\n\nSTABLE_TORCH_LIBRARY_FRAGMENT(xformers, m) {\n#if defined(USE_ROCM)\n  m.def(\n      \"efficient_attention_forward_ck(Tensor query, \"\n      \"Tensor key, Tensor value, Tensor? attn_bias, Tensor? seqstart_q, \"\n      \"Tensor? seqstart_k, int? max_seqlen_q, float dropout_p, \"\n      \"bool compute_logsumexp, int custom_mask_type, float? scale, Tensor? seqlen_k, int? window_size, Tensor? block_tables, int? page_size) -> (Tensor, Tensor?, int, int)\");\n  m.def(\n      \"efficient_attention_forward_decoder_splitk_ck(Tensor query, Tensor key, \"\n      \" Tensor value, Tensor? seq_positions, float scale, int split_k) -> Tensor\");\n  m.def(\n      \"efficient_attention_backward_ck(Tensor grad_out, Tensor query, Tensor key, Tensor value, Tensor? attn_bias, Tensor? seqstart_q, Tensor? seqstart_k, int? max_seqlen_q, int? max_seqlen_k, Tensor? seqlen_k, Tensor logsumexp, Tensor output, float dropout_p, int rng_seed, int rng_offset, int custom_mask_type, float? scale, int? window_size) -> (Tensor, Tensor, Tensor, Tensor)\");\n  m.def(\"_ck_rand_uniform(float p, Tensor out) -> Tensor\");\n#endif\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_decoder/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.26)\n\nproject(FMHADecoderMain LANGUAGES CXX HIP)\n\nmessage(\"CMAKE_CXX_COMPILER: ${CMAKE_CXX_COMPILER} (need hipcc)\")\n\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\nset(CMAKE_CXX_FLAGS \"-Wall\")\nset(CMAKE_CXX_FLAGS_DEBUG \"-g -O0\")\nset(CMAKE_VERBOSE_MAKEFILE on)\n\nset(py_version 3.9)\n\nset(exe_name attention_forward_decoder_main)\nset(splitk_exe_name attention_forward_splitk_decoder_main)\nset(project_root_dir /xformers)\nset(xformers_csrc ${project_root_dir}/xformers/csrc)\nset(sources ${xformers_csrc}/attention/hip_fmha/attention_forward_decoder.hip)\nset(splitk_sources ${xformers_csrc}/attention/hip_fmha/attention_forward_splitk.hip)\nset(ck_include ${project_root_dir}/third_party/composable_kernel_tiled/include/)\nset(torch_include /opt/conda/envs/py_${py_version}/lib/python${py_version}/site-packages/torch/include)\n\nset_source_files_properties(${sources} ${splitk_sources} PROPERTIES LANGUAGE HIP)\nadd_executable(${exe_name} ${sources})\nadd_executable(${splitk_exe_name} ${splitk_sources})\n\nfind_package(HIP REQUIRED)\nfind_package(ROCM REQUIRED PATHS /opt/rocm)\ninclude(ROCMInstallTargets)\n\nmessage(\"HIP_VERSION: ${HIP_VERSION_MAJOR}.${HIP_VERSION_MINOR}.${HIP_VERSION_PATCH}\")\n\nset_target_properties(${exe_name} ${splitk_exe_name} PROPERTIES LINKER_LANGUAGE CXX)\nset_target_properties(${exe_name} ${splitk_exe_name} PROPERTIES POSITION_INDEPENDENT_CODE ON)\nset_target_properties(${exe_name} ${splitk_exe_name} PROPERTIES HIP_ARCHITECTURES ${GPU_TARGETS})\n\ntarget_compile_options(${exe_name} PUBLIC\n  -fno-gpu-rdc\n  $<$<CONFIG:Debug>:\n  --save-temps\n  >\n)\n\ntarget_compile_options(${splitk_exe_name} PUBLIC\n  -fno-gpu-rdc\n  $<$<CONFIG:Debug>:\n  --save-temps\n  -g\n  -O0\n  >\n)\n\ntarget_include_directories(${exe_name} PUBLIC\n  ${ck_include}                           # ck includes\n  ${torch_include}                        # aten includes\n  ${torch_include}/torch/csrc/api/include # torch includes\n)\n\ntarget_include_directories(${splitk_exe_name} PUBLIC\n  ${ck_include}                           # ck includes\n  ${torch_include}                        # aten includes\n  ${torch_include}/torch/csrc/api/include # torch includes\n)\n\ntarget_link_directories(${exe_name} PUBLIC\n  /opt/conda/envs/py_${py_version}/lib/python${py_version}/site-packages/torch/lib # c10, torch\n  /opt/rocm/hip/lib\n)\n\ntarget_link_directories(${splitk_exe_name} PUBLIC\n  /opt/conda/envs/py_${py_version}/lib/python${py_version}/site-packages/torch/lib # c10, torch\n  /opt/rocm/hip/lib\n)\n\ntarget_link_libraries(${exe_name} PUBLIC\n  c10\n  c10_hip\n  torch\n  torch_hip\n  torch_cpu\n  amdhip64\n)\n\n\ntarget_link_libraries(${splitk_exe_name} PUBLIC\n  c10\n  c10_hip\n  torch\n  torch_hip\n  torch_cpu\n  amdhip64\n)\n\ntarget_compile_definitions(${exe_name} PUBLIC\n  ATTN_FWD_DECODER_MAIN=1\n  GLIBCXX_USE_CXX11_ABI=1\n  __HIP_PLATFORM_HCC__=1\n  USE_ROCM=1\n)\n\ntarget_compile_definitions(${splitk_exe_name} PUBLIC\n  ATTN_FWD_SPLITK_DECODER_MAIN=1\n  GLIBCXX_USE_CXX11_ABI=1\n  __HIP_PLATFORM_HCC__=1\n  USE_ROCM=1\n)\n\ninclude(CMakePrintHelpers)\ncmake_print_properties(TARGETS ${exe_name} ${splitk_exe_name} PROPERTIES\n  LINK_LIBRARIES\n  LINK_DIRECTORIES\n  INCLUDE_DIRECTORIES\n  COMPILE_DEFINITIONS\n  COMPILE_OPTIONS\n  SOURCES\n  HIP_ARCHITECTURES)\n\nrocm_install(TARGETS ${exe_name} ${splitk_exe_name})\n"
  },
  {
    "path": "xformers/csrc/attention/hip_decoder/attention_forward_splitk.cpp",
    "content": "#include <ATen/Dispatch.h>\n#include <ATen/Functions.h>\n#include <ATen/Tensor.h>\n#include <c10/cuda/CUDAStream.h>\n#include <torch/library.h>\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n\n#include \"ck_tile_attention_forward_decoder_splitk.h\"\n\nnamespace {\nconstexpr int32_t kThreadsPerWavefront = 64;\nconstexpr int32_t kWavefrontsPerBlock = 4;\nconstexpr int32_t kMaxHeadDimension = 4 * kThreadsPerWavefront;\nconstexpr int32_t kMaxKVSequenceLength = 4096;\nconstexpr int32_t kLoopUnroll = 16;\nconstexpr int32_t kLoopUnrollTail = 2;\nusing compute_t = float;\n} // namespace\n\nnamespace {\n\ntemplate <typename c10_t>\nstruct c10_to_data_t;\ntemplate <>\nstruct c10_to_data_t<float> {\n  using type = float;\n};\n\ntemplate <>\nstruct c10_to_data_t<c10::Half> {\n  using type = ck_tile::fp16_t;\n};\n\ntemplate <>\nstruct c10_to_data_t<c10::BFloat16> {\n  using type = ck_tile::bf16_t;\n};\n} // namespace\n\n#define AT_DISPATCH_CASE_3(SCALARTYPE1, SCALARTYPE2, SCALARTYPE3, ...) \\\n  AT_DISPATCH_CASE(SCALARTYPE1, __VA_ARGS__)                           \\\n  AT_DISPATCH_CASE(SCALARTYPE2, __VA_ARGS__)                           \\\n  AT_DISPATCH_CASE(SCALARTYPE3, __VA_ARGS__)\n\n#define AT_DISPATCH_SWITCH_3(                               \\\n    SCALARTYPE1, SCALARTYPE2, SCALARTYPE3, TYPE, NAME, ...) \\\n  AT_DISPATCH_SWITCH(                                       \\\n      TYPE,                                                 \\\n      NAME,                                                 \\\n      AT_DISPATCH_CASE_3(SCALARTYPE1, SCALARTYPE2, SCALARTYPE3, __VA_ARGS__))\n\nnamespace {\n\ntemplate <typename ck_data_t, typename compute_t, int32_t vec_size>\nvoid instantiate_and_launch_kernels(\n    typename ck_tile::ForwardDecoderSplitKArgument<ck_data_t, compute_t> arg,\n    dim3 attn_grid_size,\n    dim3 attn_block_size,\n    int32_t lds_bytes,\n    dim3 reduce_grid_size,\n    dim3 reduce_block_size,\n    hipStream_t stream) {\n  auto attn_kernel_impl = ck_tile::ForwardDecoderSplitKAttnKernelImpl<\n      ck_data_t,\n      vec_size,\n      kLoopUnroll,\n      kLoopUnrollTail,\n      kMaxKVSequenceLength,\n      compute_t>{};\n  auto reduce_kernel_impl = ck_tile::\n      ForwardDecoderSplitKReduceKernelImpl<ck_data_t, vec_size, compute_t>{};\n\n  (void)ck_tile::launch_kernel(\n      ck_tile::stream_config{stream, /* benchmark */ false},\n      ck_tile::make_kernel(\n          attn_kernel_impl, attn_grid_size, attn_block_size, lds_bytes, arg));\n\n  (void)ck_tile::launch_kernel(\n      ck_tile::stream_config{stream, /* benchmark */ false},\n      ck_tile::make_kernel(\n          reduce_kernel_impl,\n          reduce_grid_size,\n          reduce_block_size,\n          0 /* lds_bytes */,\n          arg));\n}\n\ntemplate <\n    int32_t ThreadsPerWavefront,\n    int32_t WavefrontsPerBlock>\nat::Tensor& efficient_attention_forward_decoder_splitk_ck_out_impl(\n    const at::Tensor& XQ, // [B, 1, G, H, D]\n    const at::Tensor& cache_K, // [B, kMaxKVSequenceLength, G, H or 1, D]\n    const at::Tensor& cache_V, // [B, kMaxKVSequenceLength, G, H or 1, D]\n    at::optional<at::Tensor> seq_kv_lens, // [B]\n    float qk_scale,\n    int32_t split_k,\n    at::Tensor& split_max,\n    at::Tensor& split_sumexp,\n    at::Tensor& split_O,\n    at::Tensor& O) {\n  static_assert(4 * ThreadsPerWavefront == kMaxHeadDimension, \"\");\n  static_assert(WavefrontsPerBlock <= ThreadsPerWavefront, \"\");\n\n  at::OptionalDeviceGuard guard(XQ.device());\n  TORCH_CHECK(XQ.is_cuda());\n  TORCH_CHECK(cache_K.is_cuda());\n  TORCH_CHECK(cache_V.is_cuda());\n\n  TORCH_CHECK(!seq_kv_lens || seq_kv_lens->is_cuda());\n\n  TORCH_CHECK(cache_K.size(1) / split_k <= kMaxKVSequenceLength);\n  TORCH_CHECK(cache_K.size(4) <= kMaxHeadDimension);\n\n  constexpr auto rank = 5;\n\n  auto B = XQ.size(0);\n  auto M = XQ.size(1);\n  auto G = XQ.size(2);\n  auto H = XQ.size(3);\n  auto HDim = XQ.size(4);\n\n  TORCH_CHECK(B <= 1024);\n  TORCH_CHECK(M <= 1024);\n  TORCH_CHECK(H <= 1024);\n\n  const dim3 attn_grid_size(B * H * M * G, split_k);\n  const dim3 attn_block_size(ThreadsPerWavefront, WavefrontsPerBlock);\n\n  const dim3 reduce_grid_size = {attn_grid_size.x};\n  const dim3 reduce_block_size = {attn_block_size.x};\n\n  int32_t smem_softmax = kMaxKVSequenceLength * sizeof(compute_t) +\n      WavefrontsPerBlock * sizeof(compute_t);\n  int32_t smem_output = kMaxHeadDimension * sizeof(compute_t) *\n      WavefrontsPerBlock; // 4 * threadsPerBlock * sizeof(float) ==\n                          // sizeof(O[b][0][h][:])\n  const size_t attn_lds_bytes = max(smem_softmax, smem_output);\n  auto stream = at::hip::getCurrentHIPStream().stream();\n\n  AT_DISPATCH_SWITCH_3(\n      at::ScalarType::Half,\n      at::ScalarType::BFloat16,\n      at::ScalarType::Float,\n      XQ.scalar_type(),\n      \"efficient_attention_forward_decoder_splitk_ck\",\n      [&] {\n        using ck_data_t = c10_to_data_t<scalar_t>::type;\n\n        auto XQ_acc =\n            XQ.packed_accessor32<scalar_t, rank, at::RestrictPtrTraits>();\n        auto K_acc =\n            cache_K.packed_accessor64<scalar_t, rank, at::RestrictPtrTraits>();\n        auto V_acc =\n            cache_V.packed_accessor64<scalar_t, rank, at::RestrictPtrTraits>();\n        auto split_O_acc =\n            split_O\n                .packed_accessor32<scalar_t, 1 + rank, at::RestrictPtrTraits>();\n        auto O_acc =\n            O.packed_accessor32<scalar_t, rank, at::RestrictPtrTraits>();\n        auto seq_acc_ptr = seq_kv_lens\n            ? seq_kv_lens\n                  ->packed_accessor32<int32_t, 1, at::RestrictPtrTraits>()\n                  .data()\n            : nullptr;\n        auto split_max_acc =\n            split_max.packed_accessor32<float, rank, at::RestrictPtrTraits>();\n        auto split_sumexp_acc =\n            split_sumexp\n                .packed_accessor32<float, rank, at::RestrictPtrTraits>();\n        auto arg = ck_tile::ForwardDecoderSplitKArgument<ck_data_t, compute_t>{\n            reinterpret_cast<const ck_data_t* __restrict__>(XQ_acc.data()),\n            reinterpret_cast<const ck_data_t* __restrict__>(K_acc.data()),\n            reinterpret_cast<const ck_data_t* __restrict__>(V_acc.data()),\n            reinterpret_cast<ck_data_t* __restrict__>(O_acc.data()),\n            reinterpret_cast<ck_data_t* __restrict__>(split_O_acc.data()),\n            split_max_acc.data(),\n            split_sumexp_acc.data(),\n            seq_acc_ptr,\n            XQ_acc.stride(0),\n            XQ_acc.stride(1),\n            XQ_acc.stride(2),\n            XQ_acc.stride(3),\n            K_acc.stride(0),\n            K_acc.stride(1),\n            K_acc.stride(2),\n            K_acc.stride(3),\n            split_O_acc.stride(0),\n            static_cast<int32_t>(XQ_acc.size(1)),\n            static_cast<int32_t>(XQ_acc.size(2)),\n            static_cast<int32_t>(XQ_acc.size(3)),\n            static_cast<int32_t>(XQ_acc.size(4)),\n            static_cast<int32_t>(K_acc.size(1)),\n            K_acc.size(3) == 1,\n            qk_scale,\n            split_k};\n\n        auto required_vec_size = 0;\n\n        for (auto vec_size : {4, 2, 1}) {\n          if (arg.Q_size_k <= vec_size * ThreadsPerWavefront) {\n            required_vec_size = vec_size;\n          }\n        }\n\n        TORCH_CHECK(required_vec_size > 0);\n\n        switch (required_vec_size) {\n          case 4:\n            instantiate_and_launch_kernels<ck_data_t, compute_t, 4>(\n                arg,\n                attn_grid_size,\n                attn_block_size,\n                attn_lds_bytes,\n                reduce_grid_size,\n                reduce_block_size,\n                stream);\n            break;\n          case 2:\n            instantiate_and_launch_kernels<ck_data_t, compute_t, 2>(\n                arg,\n                attn_grid_size,\n                attn_block_size,\n                attn_lds_bytes,\n                reduce_grid_size,\n                reduce_block_size,\n                stream);\n            break;\n          case 1:\n            instantiate_and_launch_kernels<ck_data_t, compute_t, 1>(\n                arg,\n                attn_grid_size,\n                attn_block_size,\n                attn_lds_bytes,\n                reduce_grid_size,\n                reduce_block_size,\n                stream);\n            break;\n          default:\n            break;\n        }\n      });\n\n  return O;\n}\n\ntemplate <int32_t ThreadsPerWavefront, int32_t WavefrontsPerBlock>\nat::Tensor efficient_attention_forward_decoder_splitk_ck_impl(\n    const at::Tensor& XQ, // [B, 1, G, H, D]\n    const at::Tensor& cache_K, // [B, kMaxKVSequenceLength, G, H or 1, D]\n    const at::Tensor& cache_V, // [B, kMaxKVSequenceLength, H or 1, D]\n    at::optional<at::Tensor> seq_kv_lens, // [B]\n    float qk_scale,\n    int32_t split_k) {\n  auto O = at::empty_like(XQ);\n  constexpr auto rank = 5;\n\n  TORCH_CHECK(XQ.dim() == rank);\n  TORCH_CHECK(cache_K.dim() == rank);\n  TORCH_CHECK(cache_V.dim() == rank);\n\n  auto B = XQ.size(0);\n  auto M = XQ.size(1);\n  auto G = XQ.size(2);\n  auto H = XQ.size(3);\n  auto K = XQ.size(4);\n\n  auto O_splits = at::empty({split_k, B, M, G, H, K}, XQ.options());\n  auto split_max =\n      at::empty({B, M, G, H, split_k}, XQ.options().dtype(at::kFloat));\n  auto split_sumexp = at::empty_like(split_max);\n\n  efficient_attention_forward_decoder_splitk_ck_out_impl<\n      ThreadsPerWavefront,\n      WavefrontsPerBlock>(\n      XQ,\n      cache_K,\n      cache_V,\n      seq_kv_lens,\n      qk_scale,\n      split_k,\n      split_max,\n      split_sumexp,\n      O_splits,\n      O);\n\n  return O;\n}\n\nat::Tensor efficient_attention_forward_decoder_splitk_ck(\n    const at::Tensor& XQ, // [B, 1, G, H, D]\n    const at::Tensor& cache_K, // [B, kMaxKVSequenceLength, G, H or 1, D]\n    const at::Tensor& cache_V, // [B, kMaxKVSequenceLength, G, H or 1, D]\n    at::optional<at::Tensor> seq_kv_lens, // [B]\n    double qk_scale,\n    int64_t split_k) {\n  return efficient_attention_forward_decoder_splitk_ck_impl<\n      kThreadsPerWavefront,\n      kWavefrontsPerBlock>(\n      XQ,\n      cache_K,\n      cache_V,\n      seq_kv_lens,\n      static_cast<float>(qk_scale),\n      static_cast<int32_t>(split_k));\n}\n} // namespace\n\nTORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\n          \"xformers::efficient_attention_forward_decoder_splitk_ck\"),\n      TORCH_FN(efficient_attention_forward_decoder_splitk_ck));\n}\n\n#undef AT_DISPATCH_CASE_3\n#undef AT_DISPATCH_SWITCH_3\n"
  },
  {
    "path": "xformers/csrc/attention/hip_decoder/ck_tile_attention_forward_decoder_splitk.h",
    "content": "#pragma once\n\n#include <ck_tile/core.hpp>\n\n#include \"ck_tile_attention_inner_product.h\"\n\nnamespace {\n\ntemplate <typename data_t, int32_t vec_size>\n__device__ ck_tile::ext_vector_t<float, vec_size> scalar_scale_acc(\n    ck_tile::ext_vector_t<float, vec_size> acc,\n    ck_tile::ext_vector_t<data_t, vec_size> a,\n    float b) {\n  union {\n    decltype(acc) vec;\n    float arr[vec_size];\n  } acc_u{acc};\n  union {\n    decltype(a) vec;\n    data_t arr[vec_size];\n  } a_u{a};\n\n#pragma unroll\n  for (int32_t i = 0; i < vec_size; ++i) {\n    acc_u.arr[i] += ck_tile::type_convert<float>(a_u.arr[i]) * b;\n  }\n\n  return acc_u.vec;\n}\n\ntemplate <typename F, int32_t n_threads_per_wavefront = 64>\nfloat __device__ __forceinline__ wavefrontReduce(float val, F f) {\n#pragma unroll\n  for (int32_t mask = n_threads_per_wavefront >> 1; mask > 0; mask >>= 1) {\n    val = f(__shfl_xor(val, mask, n_threads_per_wavefront), val);\n  }\n  return val;\n}\n\ntemplate <typename TData, typename TDataVec>\n__forceinline__ __device__ void load_v(\n    const TData* __restrict__ data_ptr,\n    int32_t vector_offset,\n    TDataVec* __restrict__ load_to) {\n  *load_to = *(reinterpret_cast<const TDataVec*>(data_ptr) + vector_offset);\n}\n\ntemplate <typename TData, typename TDataVec>\n__forceinline__ __device__ void store_v(\n    TData* __restrict__ data_ptr,\n    int32_t vector_offset,\n    TDataVec value) {\n  *(reinterpret_cast<TDataVec*>(data_ptr) + vector_offset) = value;\n}\n\n} // namespace\n\nnamespace ck_tile {\ntemplate <typename scalar_t, typename compute_t>\nstruct ForwardDecoderSplitKArgument {\n  const scalar_t* __restrict__ XQ;\n  const scalar_t* __restrict__ cache_K;\n  const scalar_t* __restrict__ cache_V;\n  scalar_t* __restrict__ O;\n  scalar_t* __restrict__ split_O;\n  compute_t* __restrict__ split_max;\n  compute_t* __restrict__ split_sumexp;\n  const int32_t* __restrict__ seq_kv_lens;\n  const ptrdiff_t XQ_stride_b;\n  const ptrdiff_t XQ_stride_m;\n  const ptrdiff_t XQ_stride_g;\n  const ptrdiff_t XQ_stride_h;\n  const ptrdiff_t K_stride_b;\n  const ptrdiff_t K_stride_m;\n  const ptrdiff_t K_stride_g;\n  const ptrdiff_t K_stride_h;\n  const ptrdiff_t O_stride_split;\n  const int32_t Q_size_m;\n  const int32_t Q_size_g;\n  const int32_t Q_size_h;\n  const int32_t Q_size_k;\n  const int32_t K_size_m;\n  const bool multiquery;\n  const float qk_scale;\n  const int32_t split_k;\n};\n\ntemplate <typename scalar_t, int32_t vec_size = 4, typename compute_t = float>\nstruct ForwardDecoderSplitKReduceKernelImpl {\n  CK_TILE_DEVICE void operator()(\n      ForwardDecoderSplitKArgument<scalar_t, compute_t> arg) {\n    // Each block handles a single batch and head and query and group\n    const int32_t b = blockIdx.x / (arg.Q_size_m * arg.Q_size_g * arg.Q_size_h);\n    const int32_t m =\n        (blockIdx.x / (arg.Q_size_g * arg.Q_size_h)) % arg.Q_size_m;\n    const int32_t g = (blockIdx.x / arg.Q_size_h) % arg.Q_size_g;\n    const int32_t h = blockIdx.x % arg.Q_size_h;\n\n    using data_t = scalar_t;\n    using data_vec_t = ck_tile::ext_vector_t<data_t, vec_size>;\n    using compute_vec_t = ck_tile::ext_vector_t<compute_t, vec_size>;\n\n    union {\n      data_vec_t vec;\n      data_t arr[vec_size];\n    } O_split_data;\n    union {\n      compute_vec_t vec;\n      compute_t arr[vec_size];\n    } O_split_compute;\n    union {\n      data_vec_t vec;\n      data_t arr[vec_size];\n    } global_O_data;\n    union {\n      compute_vec_t vec;\n      compute_t arr[vec_size];\n    } global_O_compute;\n\n    global_O_compute.vec = 0;\n\n    const int32_t lane_idx = threadIdx.x;\n    const bool lane_active_for_io = lane_idx * vec_size < arg.Q_size_k;\n\n    if (!lane_active_for_io) {\n      return;\n    }\n\n    compute_t global_sumexp = 0;\n    compute_t global_max = ck_tile::numeric<compute_t>::lowest();\n\n    for (int32_t split_idx = 0; split_idx < arg.split_k; ++split_idx) {\n      load_v<data_t, data_vec_t>(\n          arg.split_O + b * arg.XQ_stride_b + m * arg.XQ_stride_m +\n              g * arg.XQ_stride_g + h * arg.XQ_stride_h +\n              split_idx * arg.O_stride_split,\n          lane_idx,\n          &O_split_data.vec);\n#pragma unroll\n      for (int32_t i = 0; i < vec_size; ++i) {\n        O_split_compute.arr[i] =\n            ck_tile::type_convert<compute_t>(O_split_data.arr[i]);\n      }\n      compute_t local_max =\n          *(arg.split_max + blockIdx.x * arg.split_k + split_idx);\n      compute_t local_sumexp =\n          *(arg.split_sumexp + blockIdx.x * arg.split_k + split_idx);\n\n      compute_t log_alpha = -std::abs(local_max - global_max);\n      compute_t alpha =\n          ck_tile::isnan(log_alpha) ? compute_t{1.} : ck_tile::exp(log_alpha);\n\n      bool pick_new = local_max < global_max;\n      compute_t pick_current_coef = pick_new ? 1. : alpha;\n      compute_t pick_new_coef = pick_new ? alpha : 1.;\n\n      global_sumexp =\n          pick_current_coef * global_sumexp + pick_new_coef * local_sumexp;\n      global_O_compute.vec = pick_current_coef * global_O_compute.vec +\n          pick_new_coef * O_split_compute.vec;\n      global_max = ck_tile::max(local_max, global_max);\n    }\n    global_O_compute.vec /= global_sumexp;\n#pragma unroll\n    for (int32_t i = 0; i < vec_size; ++i) {\n      global_O_data.arr[i] =\n          ck_tile::type_convert<data_t>(global_O_compute.arr[i]);\n    }\n    store_v<data_t, data_vec_t>(\n        arg.O + b * arg.XQ_stride_b + m * arg.XQ_stride_m +\n            g * arg.XQ_stride_g + h * arg.XQ_stride_h,\n        lane_idx,\n        global_O_data.vec);\n  }\n};\n\ntemplate <\n    typename scalar_t,\n    int32_t vec_size,\n    int32_t n_loop_unroll,\n    int32_t n_loop_unroll_tail,\n    int32_t KV_M_MAX,\n    typename compute_t>\nstruct ForwardDecoderSplitKAttnKernelImpl {\n  CK_TILE_DEVICE void operator()(\n      ForwardDecoderSplitKArgument<scalar_t, compute_t> arg) {\n    static_assert(\n        n_loop_unroll_tail < n_loop_unroll || n_loop_unroll_tail == 1,\n        \"tail unroll must be smaller than main loop untoll; pragma unroll 0 is illegal \"\n        \"(and tail is no-op)\");\n\n    // Each block handles a single batch and head and query and group\n    const int32_t b = blockIdx.x / (arg.Q_size_m * arg.Q_size_g * arg.Q_size_h);\n    const int32_t m =\n        (blockIdx.x / (arg.Q_size_g * arg.Q_size_h)) % arg.Q_size_m;\n    const int32_t g = (blockIdx.x / arg.Q_size_h) % arg.Q_size_g;\n    const int32_t h = blockIdx.x % arg.Q_size_h;\n    const int32_t split_idx = blockIdx.y;\n\n    // Note: this is decoding case where we attend to current and all previous\n    // tokens.\n    const int32_t t_max = arg.seq_kv_lens ? arg.seq_kv_lens[b] : arg.K_size_m;\n\n    const int32_t lane_idx = threadIdx.x;\n    const int32_t wavefront_idx = threadIdx.y;\n    // TODO: `threads_per_wavefront` and `wavefronts_per_block` may be compile\n    // time constants; investigate when optimizing\n    const int32_t threads_per_wavefront = blockDim.x;\n    const int32_t wavefronts_per_block = blockDim.y;\n    const int32_t threads_per_block =\n        threads_per_wavefront * wavefronts_per_block;\n    const int32_t thread_linear_idx =\n        lane_idx + wavefront_idx * threads_per_wavefront;\n    // const auto* q_ = &(XQ_acc[b][m][g][h][0]);\n    const auto XQO_base_offset = b * arg.XQ_stride_b + m * arg.XQ_stride_m +\n        g * arg.XQ_stride_g + h * arg.XQ_stride_h;\n    const auto* __restrict__ q_ = arg.XQ + XQO_base_offset;\n\n    const auto cache_KV_base_offset = b * arg.K_stride_b + 0 * arg.K_stride_m +\n        g * arg.K_stride_g + (arg.multiquery ? 0 : h * arg.K_stride_h);\n    const auto* __restrict__ cache_K_base = arg.cache_K + cache_KV_base_offset;\n    const auto* __restrict__ cache_V_base = arg.cache_V + cache_KV_base_offset;\n\n    using data_t = scalar_t;\n    using data_vec_t = std::conditional_t<\n        vec_size == 1,\n        data_t,\n        ck_tile::ext_vector_t<data_t, vec_size>>;\n    using compute_vec_t = ck_tile::ext_vector_t<compute_t, vec_size>;\n\n    const bool lane_active_for_io = lane_idx * vec_size < arg.Q_size_k;\n\n    extern __shared__ __align__(16) compute_t smem[];\n\n    data_vec_t q_thread = 0;\n    // Load Q into registers in all wavefronts.\n    // Each thread handles `vec_size` D dimensions\n    if (lane_active_for_io) {\n      load_v<data_t, data_vec_t>(q_, lane_idx, &q_thread);\n    }\n\n    compute_t max_qk_acc = ck_tile::numeric<compute_t>::lowest();\n\n    // Compute S[0:t_max] =\n    // ```\n    // for t in range(t_max):\n    //   S[t] = dot(Q, K[t])\n    // ```\n    // Split the 0:t_max range across wavefronts in a block,\n    // unroll loads to expose more parallelism.\n    // Reduce the dot product with cross-lane operation;\n    // Q and K[t] are in the registers of threads in a single wavefront.\n\n    data_vec_t k_loads[n_loop_unroll] = {};\n\n    const auto dtt = wavefronts_per_block * n_loop_unroll;\n    // only last split gets the tail.\n    // the first (split_k - 1) splits have a number of iterations divisible by\n    // `dtt`\n    const auto n_unrolled_loops = t_max / dtt / arg.split_k; // +1?\n    const int32_t tt_low =\n        wavefront_idx * n_loop_unroll + n_unrolled_loops * dtt * split_idx;\n    const int32_t tt_high = wavefront_idx * n_loop_unroll +\n        n_unrolled_loops * dtt * (split_idx + 1);\n    const int32_t dtt_tail = wavefronts_per_block * n_loop_unroll_tail;\n    const int32_t tt_tail_low = wavefront_idx * n_loop_unroll_tail +\n        n_unrolled_loops * dtt * (split_idx + 1);\n    const int32_t tt_tail_high =\n        (split_idx == arg.split_k - 1) ? t_max : tt_tail_low;\n\n    for (auto tt = tt_low; tt < tt_high; tt += dtt) {\n      if (lane_active_for_io) {\n#pragma unroll n_loop_unroll\n        for (auto ttt = 0; ttt < n_loop_unroll; ++ttt) {\n          const int32_t t = tt + ttt;\n          // load the K[b][t][g][h|0][:] row into registers\n          load_v<data_t, data_vec_t>(\n              cache_K_base + t * arg.K_stride_m, lane_idx, &k_loads[ttt]);\n        }\n      }\n#pragma unroll n_loop_unroll\n      for (auto ttt = 0; ttt < n_loop_unroll; ++ttt) {\n        compute_t qk_acc = 0;\n        ck_tile::inner_product<data_vec_t, data_vec_t, compute_t>(\n            q_thread, k_loads[ttt], qk_acc);\n        qk_acc *= arg.qk_scale;\n\n        qk_acc = wavefrontReduce(qk_acc, [](auto a, auto b) { return a + b; });\n        max_qk_acc = ck_tile::max(qk_acc, max_qk_acc);\n        if (lane_idx == 0) {\n          smem[tt + ttt - n_unrolled_loops * dtt * split_idx] = qk_acc;\n        }\n      }\n    }\n\n    for (auto tt = tt_tail_low; tt < tt_tail_high; tt += dtt_tail) {\n      if (lane_active_for_io) {\n#pragma unroll n_loop_unroll_tail\n        for (auto ttt = 0; ttt < n_loop_unroll_tail; ++ttt) {\n          const int32_t t = tt + ttt;\n          if (t < t_max) {\n            // load the K[b][t][g][h|0][:] row into registers\n            load_v<data_t, data_vec_t>(\n                cache_K_base + t * arg.K_stride_m, lane_idx, &k_loads[ttt]);\n          }\n        }\n      }\n#pragma unroll n_loop_unroll_tail\n      for (auto ttt = 0; ttt < n_loop_unroll_tail; ++ttt) {\n        compute_t qk_acc = 0;\n        const int32_t t = tt + ttt;\n        if (t < t_max) {\n          ck_tile::inner_product<data_vec_t, data_vec_t, compute_t>(\n              q_thread, k_loads[ttt], qk_acc);\n          qk_acc *= arg.qk_scale;\n\n          qk_acc =\n              wavefrontReduce(qk_acc, [](auto a, auto b) { return a + b; });\n          max_qk_acc = ck_tile::max(qk_acc, max_qk_acc);\n\n          // write accumulated sums to smem.\n          if (lane_idx == 0) {\n            smem[t - n_unrolled_loops * dtt * split_idx] = qk_acc;\n          }\n        }\n      }\n    }\n\n    // Use shared reduction to compute max and compute softmax on shared memory.\n    // write max acc\n    if (lane_idx == 0) {\n      smem[KV_M_MAX + wavefront_idx] = max_qk_acc;\n    }\n    __syncthreads();\n    if (lane_idx < wavefronts_per_block) {\n      max_qk_acc = ck_tile::max(max_qk_acc, smem[KV_M_MAX + lane_idx]);\n    }\n    // shared across all threads in block\n    max_qk_acc = wavefrontReduce(\n        max_qk_acc, [](auto a, auto b) { return a > b ? a : b; });\n\n    if (wavefront_idx == 0 && lane_idx == 0) {\n      arg.split_max[blockIdx.x * arg.split_k + split_idx] = max_qk_acc;\n    }\n\n    // each wavefront computes partial sum of exp.\n    { // softmax reduce begin\n      compute_t softmax_denominator = 0.0f;\n      const int32_t t_low = n_unrolled_loops * dtt * split_idx;\n      const int32_t t_high = (split_idx + 1 < arg.split_k)\n          ? n_unrolled_loops * dtt * (split_idx + 1)\n          : t_max;\n      for (int32_t t = t_low + thread_linear_idx; t < t_high;\n           t += threads_per_block) {\n        const auto s = ck_tile::exp(smem[t - t_low] - max_qk_acc);\n        softmax_denominator += s;\n        smem[t - t_low] = s;\n      }\n      softmax_denominator = wavefrontReduce(\n          softmax_denominator, [](auto a, auto b) { return a + b; });\n\n      if (lane_idx == 0) {\n        smem[KV_M_MAX + wavefront_idx] = softmax_denominator;\n      }\n      __syncthreads();\n\n      // now, compute sum of exp(x - max(x)) over all intermediate results.\n      softmax_denominator = 0.0;\n      if (lane_idx < wavefronts_per_block) {\n        softmax_denominator = smem[KV_M_MAX + lane_idx];\n      }\n      softmax_denominator = wavefrontReduce(\n          softmax_denominator, [](auto a, auto b) { return a + b; });\n\n      if (wavefront_idx == 0 && lane_idx == 0) {\n        arg.split_sumexp[blockIdx.x * arg.split_k + split_idx] =\n            softmax_denominator;\n      }\n    } // softmax reduce end\n\n    // Split T across wavefronts in a block\n    // each wavefront compute sum(t_subset) P[t] * V[t_subset, d]\n    // outputs are of size float[D]\n\n    compute_t ps[n_loop_unroll] = {};\n    compute_vec_t o_acc = 0;\n    if (lane_active_for_io) {\n      for (auto tt = tt_low; tt < tt_high; tt += dtt) {\n#pragma unroll n_loop_unroll\n        for (auto ttt = 0; ttt < n_loop_unroll; ++ttt) {\n          const int32_t t = tt + ttt;\n          // load the V[b][t][g][h|0][:] row into registers, reusing K register\n          // storage\n          load_v<data_t, data_vec_t>(\n              cache_V_base + t * arg.K_stride_m, lane_idx, &k_loads[ttt]);\n          ps[ttt] = smem[t - n_unrolled_loops * dtt * split_idx];\n        }\n\n#pragma unroll n_loop_unroll\n        for (auto ttt = 0; ttt < n_loop_unroll; ++ttt) {\n          o_acc =\n              scalar_scale_acc<data_t, vec_size>(o_acc, k_loads[ttt], ps[ttt]);\n        }\n      }\n\n      for (auto tt = tt_tail_low; tt < tt_tail_high; tt += dtt_tail) {\n#pragma unroll n_loop_unroll_tail\n        for (auto ttt = 0; ttt < n_loop_unroll_tail; ++ttt) {\n          const int32_t t = tt + ttt;\n          if (t < t_max) {\n            // load the V[b][t][g][h|0][:] row into registers, reusing K\n            // register storage\n            load_v<data_t, data_vec_t>(\n                cache_V_base + t * arg.K_stride_m, lane_idx, &k_loads[ttt]);\n            ps[ttt] = smem[t - n_unrolled_loops * dtt * split_idx];\n            o_acc = scalar_scale_acc<data_t, vec_size>(\n                o_acc, k_loads[ttt], ps[ttt]);\n          }\n        }\n      }\n    }\n    __syncthreads();\n\n    // NB: needs sizeof(smem) >= `vec_size` * (sizeof(float)==4) *\n    // threadsPerBlock\n    if (lane_active_for_io) {\n      store_v<compute_t, compute_vec_t>(&smem[0], thread_linear_idx, o_acc);\n    }\n\n    __syncthreads();\n    // sum up partial D rows from other wavefronts\n    if (wavefront_idx == 0 && lane_active_for_io) {\n      union {\n        compute_vec_t vec = 0;\n        compute_t arr[vec_size];\n      } r;\n      for (int32_t w = 0; w < wavefronts_per_block; ++w) {\n        compute_vec_t partial_r;\n        load_v<compute_t, compute_vec_t>(\n            smem, w * threads_per_wavefront + lane_idx, &partial_r);\n        r.vec += partial_r;\n      }\n      // elementwise convert from compute_t result to data_t out to be written\n      union {\n        data_vec_t vec;\n        data_t arr[vec_size];\n      } bf_r;\n#pragma unroll\n      for (int32_t i = 0; i < vec_size; ++i) {\n        bf_r.arr[i] = ck_tile::type_convert<data_t>(r.arr[i]);\n      }\n      // write output row O[b][m][g][h][:]\n      data_t* __restrict__ o_ =\n          arg.split_O + XQO_base_offset + split_idx * arg.O_stride_split;\n      store_v<data_t, data_vec_t>(o_, lane_idx, bf_r.vec);\n    }\n  }\n};\n\n} // namespace ck_tile\n"
  },
  {
    "path": "xformers/csrc/attention/hip_decoder/ck_tile_attention_inner_product.h",
    "content": "/*\n * Copyright (c) 2023-2025, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n\nnamespace ck_tile {\n\ntemplate <typename TA, typename TB, typename TC>\nCK_TILE_DEVICE void inner_product(const TA& a, const TB& b, TC& c);\n\ntemplate <typename TA, typename TB, typename TC, typename TItem>\nCK_TILE_DEVICE void inner_product_unrolled(const TA& a, const TB& b, TC& c) {\n  static_assert(std::is_same_v<TA, TB>);\n  constexpr int unroll_count = sizeof(TA) / sizeof(TItem);\n  using item_array_t = TItem[unroll_count];\n  auto av = *reinterpret_cast<const item_array_t*>(&a);\n  auto bv = *reinterpret_cast<const item_array_t*>(&b);\n#pragma unroll\n  for (int i = 0; i < unroll_count; ++i) {\n    inner_product(av[i], bv[i], c);\n  }\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<float, float, float>(\n    const float& a,\n    const float& b,\n    float& c) {\n#if (defined(__gfx9__)) // for GPU code\n  asm volatile(\n      \"\\n \\\n            v_fmac_f32 %0, %1, %2 \\n \\\n            \"\n      : \"=v\"(c)\n      : \"v\"(a), \"v\"(b), \"0\"(c));\n#else\n  c += a * b;\n#endif\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp32x2_t, fp32x2_t, float>(\n    const fp32x2_t& a,\n    const fp32x2_t& b,\n    float& c) {\n  inner_product_unrolled<fp32x2_t, fp32x2_t, float, fp32_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp32x4_t, fp32x4_t, float>(\n    const fp32x4_t& a,\n    const fp32x4_t& b,\n    float& c) {\n  inner_product_unrolled<fp32x4_t, fp32x4_t, float, fp32_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<bf16_t, bf16_t, float>(\n    const bf16_t& a,\n    const bf16_t& b,\n    float& c) {\n  inner_product(type_convert<float>(a), type_convert<float>(b), c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp16_t, fp16_t, float>(\n    const fp16_t& a,\n    const fp16_t& b,\n    float& c) {\n  inner_product(type_convert<float>(a), type_convert<float>(b), c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp16x2_t, fp16x2_t, float>(\n    const fp16x2_t& a,\n    const fp16x2_t& b,\n    float& c) {\n#if (defined(__gfx9__)) // for GPU code\n  c = __builtin_amdgcn_fdot2(a, b, c, false);\n#else\n  inner_product_unrolled<fp16x2_t, fp16x2_t, float, fp16_t>(a, b, c);\n#endif\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp16x4_t, fp16x4_t, float>(\n    const fp16x4_t& a,\n    const fp16x4_t& b,\n    float& c) {\n  inner_product_unrolled<fp16x4_t, fp16x4_t, float, fp16x2_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<fp16x8_t, fp16x8_t, float>(\n    const fp16x8_t& a,\n    const fp16x8_t& b,\n    float& c) {\n  inner_product_unrolled<fp16x8_t, fp16x8_t, float, fp16x2_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<bf16x2_t, bf16x2_t, float>(\n    const bf16x2_t& a,\n    const bf16x2_t& b,\n    float& c) {\n  inner_product_unrolled<bf16x2_t, bf16x2_t, float, bf16_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<bf16x4_t, bf16x4_t, float>(\n    const bf16x4_t& a,\n    const bf16x4_t& b,\n    float& c) {\n  inner_product_unrolled<bf16x4_t, bf16x4_t, float, bf16_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<int8_t, int8_t, int32_t>(\n    const int8_t& a,\n    const int8_t& b,\n    int32_t& c) {\n  c += type_convert<int32_t>(a) * type_convert<int32_t>(b);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<int8x2_t, int8x2_t, int32_t>(\n    const int8x2_t& a,\n    const int8x2_t& b,\n    int32_t& c) {\n  inner_product_unrolled<int8x2_t, int8x2_t, int32_t, int8_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<int8x4_t, int8x4_t, int32_t>(\n    const int8x4_t& a,\n    const int8x4_t& b,\n    int32_t& c) {\n#if (defined(__gfx9__)) // for GPU code\n  c = __builtin_amdgcn_sdot4(\n      bit_cast<int32_t>(a), bit_cast<int32_t>(b), c, false);\n#else\n  inner_product_unrolled<int8x4_t, int8x4_t, int32_t, int8_t>(a, b, c);\n#endif\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<int8x8_t, int8x8_t, int32_t>(\n    const int8x8_t& a,\n    const int8x8_t& b,\n    int32_t& c) {\n  inner_product_unrolled<int8x8_t, int8x8_t, int32_t, int8x4_t>(a, b, c);\n}\n\ntemplate <>\nCK_TILE_DEVICE void inner_product<int8x16_t, int8x16_t, int32_t>(\n    const int8x16_t& a,\n    const int8x16_t& b,\n    int32_t& c) {\n  inner_product_unrolled<int8x16_t, int8x16_t, int32_t, int8x4_t>(a, b, c);\n}\n\n// TBD: Packed I4\n\n} // namespace ck_tile\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/GENERATE_INSTANCES.md",
    "content": "\n# Instances generator\n\n  The instances generator is a simple python tool used to generate several hundred of instances (.cpp files) and their references (.h files).\n  Without this tool, manually writing those instances and references will be very laborious and easy to get wrong.\n\n  The instances generated by this scripts are divided into three categories visible from the scripts:\n   * Infer -- which refers to instances for calling inference-only kernels\n   * Forward -- which refers to instances for calling training forward kernels\n   * Backward -- which refers to instances for calling training backward kernels\n\n  The instance generator is for being used by the HIP fmha developers themselves. It is not supposed to be used by the xformers users for\n  building xformers, since for xformers users, the instances are already well prepared as part of the xformers codes.\n\n## how to use instance generator\n\n   * To generate complete instances supported by current implementation\n\n     ```\n      #> python xformers/csrc/attention/hip_fmha/generate_instances.py\n     ```\n   * To generate reduced instances (when headdim256 is not required)\n\n     ```\n      #> python xformers/csrc/attention/hip_fmha/generate_instances.py --ignore-hd256\n     ```\n   * More options except for `--ignore-hd256` could be added to suppport further customization in generating instances as required\n\n## where the instances files are located\n   The instances files and references files are always located under a folder `instances/` that is located under the same directory\n   as the file `generate_instances.py` itself\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/attention_backward_generic_ck_tiled.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <cmath>\n#include <cstdlib>\n\n#include <ATen/Context.h>\n#include <ATen/ScalarOps.h>\n#include <ATen/Tensor.h>\n#include <ATen/TensorOperators.h>\n#include <c10/hip/HIPStream.h>\n#include <torch/library.h>\n#include <ATen/cuda/PhiloxUtils.cuh>\n\n#include \"ck_fmha_util.h\"\n#include \"ck_tiled_fmha_params.h\"\n\nextern void batched_backward_fp16(\n    BatchedBackwardParams& param,\n    hipStream_t stream);\nextern void batched_backward_bf16(\n    BatchedBackwardParams& param,\n    hipStream_t stream);\nextern void grouped_backward_fp16(\n    GroupedBackwardParams& param,\n    hipStream_t stream);\nextern void grouped_backward_bf16(\n    GroupedBackwardParams& param,\n    hipStream_t stream);\n\nnamespace {\n\nstd::tuple<at::Tensor, at::Tensor, at::Tensor, at::Tensor>\nefficient_attention_backward_ck(\n    const at::Tensor& grad_out,\n    const at::Tensor& query,\n    const at::Tensor& key,\n    const at::Tensor& value,\n    const c10::optional<at::Tensor>& bias, // additive attention bias\n    // (Mode 1MHK only) [b+1]: cu_seqlens_q[b] contains the\n    // position of the first query token for batch $b\n    const c10::optional<at::Tensor>& seqstart_q,\n    // (Mode 1MHK only) [b+1]: cu_seqlens_k[b] contains the\n    // position of the first key token for batch $b\n    const c10::optional<at::Tensor>& seqstart_k,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_q_,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_k_,\n    const c10::optional<at::Tensor>& seqlen_k,\n    const at::Tensor& logsumexp,\n    const at::Tensor& out,\n    double dropout_p, // dropout probability\n    int64_t rng_seed, // seed using for generating random numbers for dropout\n    int64_t rng_offset, // offset into random number sequence\n    int64_t custom_mask_type,\n    const c10::optional<double> scale,\n    const c10::optional<int64_t> window_size) {\n  // ndim\n  TORCH_CHECK(query.dim() == grad_out.dim());\n  TORCH_CHECK(query.dim() == key.dim());\n  TORCH_CHECK(query.dim() == value.dim());\n  TORCH_CHECK(query.dim() == 4);\n\n  // batch size\n  TORCH_CHECK(query.size(0) == grad_out.size(0));\n  TORCH_CHECK(query.size(0) == key.size(0));\n  TORCH_CHECK(query.size(0) == value.size(0));\n\n  // seqlen\n  TORCH_CHECK(key.size(1) == value.size(1));\n  TORCH_CHECK(query.size(1) == grad_out.size(1));\n\n  // Num heads\n  TORCH_CHECK(query.size(2) % key.size(2) == 0);\n  TORCH_CHECK(key.size(2) == value.size(2));\n  TORCH_CHECK(query.size(2) == grad_out.size(2));\n\n  // Embedding per head\n  TORCH_CHECK(query.size(3) == key.size(3));\n  TORCH_CHECK(value.size(3) == grad_out.size(3));\n\n  TORCH_CHECK(out.sizes() == grad_out.sizes());\n\n  // last dim is contiguous, device is CUDA\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(out);\n  // CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(grad_out);\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(query);\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(key);\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(value);\n\n  // logsumexp should be completely contiguous\n  CHECK_NOSPARSE_CONTIGUOUS_CUDA(logsumexp);\n\n  TORCH_CHECK(seqstart_q.has_value() == seqstart_k.has_value());\n  TORCH_CHECK(\n      !(seqstart_q.has_value() && bias.has_value()),\n      \"seqstart_q + bias not supported\");\n\n  if (seqstart_q.has_value()) {\n    TORCH_CHECK(seqstart_q->scalar_type() == at::ScalarType::Int);\n    TORCH_CHECK(seqstart_k->scalar_type() == at::ScalarType::Int);\n    TORCH_CHECK(seqstart_q->dim() == 1 && seqstart_k->dim() == 1);\n    CHECK_NOSPARSE_CONTIGUOUS_CUDA((*seqstart_q));\n    CHECK_NOSPARSE_CONTIGUOUS_CUDA((*seqstart_k));\n    TORCH_CHECK(seqstart_q->size(0) == seqstart_k->size(0));\n    TORCH_CHECK(query.size(0) == 1, \"seqstart_q only supports batch_size=1\");\n    TORCH_CHECK(max_seqlen_q_.has_value());\n    TORCH_CHECK(max_seqlen_k_.has_value());\n  }\n\n  hipStream_t stream = c10::hip::getCurrentHIPStream().stream();\n\n  int64_t B = query.size(0);\n  int64_t M = query.size(1);\n  int64_t N = key.size(1);\n  int64_t Hq = query.size(2);\n  int64_t Hkv = key.size(2);\n  int64_t K = query.size(3);\n  int64_t Kv = value.size(3);\n\n  auto opts = query.options();\n\n  at::Tensor grad_q, grad_k, grad_v, grad_bias;\n\n  if (query.size(1) == key.size(1) && query.size(3) == value.size(3) &&\n      query.size(2) == key.size(2) &&\n      query.storage().is_alias_of(key.storage()) &&\n      query.storage().is_alias_of(value.storage())) {\n    // Create one big contiguous chunk for grad_q, grad_k, grad_v\n    // This is because q, k and v usually come from a single\n    // output of a linear layer that is chunked.\n    // Creating the gradients with the right layout saves us\n    // a `torch.cat` call in the backward pass\n    at::Tensor chunk = at::empty({B, M, 3, Hq, K}, opts);\n    grad_q = chunk.select(2, 0);\n    grad_k = chunk.select(2, 1);\n    grad_v = chunk.select(2, 2);\n  } else if (\n      key.size(3) == value.size(3) &&\n      key.storage().is_alias_of(value.storage())) {\n    // Create one big contiguous chunk for grad_k, grad_v\n    // This is because k and v usually come from a single\n    // output of a linear layer that is chunked.\n    // Creating the gradients with the right layout saves us\n    // a `torch.cat` call in the backward pass\n    at::Tensor chunk = at::empty({B, N, 2, Hkv, Kv}, opts);\n    grad_k = chunk.select(2, 0);\n    grad_v = chunk.select(2, 1);\n\n    grad_q = at::empty_strided(query.sizes(), query.strides(), query.options());\n  } else {\n    grad_q = at::empty_strided(query.sizes(), query.strides(), query.options());\n    grad_k = at::empty(key.sizes(), key.options());\n    grad_v = at::empty(value.sizes(), value.options());\n  }\n\n  at::Tensor grad_q_f32;\n  const bool use_grad_q_f32 =\n      (query.scalar_type() == at::ScalarType::BFloat16 ||\n       query.scalar_type() == at::ScalarType::Half);\n\n  if (use_grad_q_f32) {\n    grad_q_f32 = at::empty(grad_q.sizes(), opts.dtype(at::kFloat));\n    grad_q_f32.fill_(0);\n  } else {\n    grad_q.fill_(0);\n  };\n\n  // CK-FlashAttn requires q/k/v to have same shapes with dQ/dK/dV respectively\n  TORCH_CHECK(query.sizes() == grad_q.sizes());\n  TORCH_CHECK(query.strides() == grad_q.strides());\n  TORCH_CHECK(key.sizes() == grad_k.sizes());\n  TORCH_CHECK(value.sizes() == grad_v.sizes());\n\n  const bool bias_requires_grad = bias.has_value() && bias->requires_grad();\n\n  // even it is an output, the grad_bias is required to use the same data-type\n  // as bias in CK-FlashAttn\n  if (bias_requires_grad) {\n    grad_bias =\n        at::empty_strided(bias->sizes(), bias->strides(), bias->options());\n    // cleaning is needed since masked tile does no outputting in our\n    // implementation\n    grad_bias.fill_(0);\n  }\n\n  bool is_mqa_gqa = (Hq > Hkv);\n\n  at::Tensor tmp_grad_k, tmp_grad_v;\n\n  if (is_mqa_gqa) {\n    // allocate tmp_grad_k/tmp_grad_v which will be reduce to\n    // grad_k/grad_v for returning\n    tmp_grad_k = at::empty({B, N, Hq, K}, opts);\n    tmp_grad_v = at::empty({B, N, Hq, Kv}, opts);\n  }\n\n  auto dot_out = at::empty_like(logsumexp);\n\n  auto set_batched_backward_params = [&](BatchedBackwardParams& p) {\n    p.B = B;\n    p.M = M;\n    p.N = N;\n    p.Hq = Hq;\n    p.Hkv = Hkv;\n    p.K = K;\n    p.Kv = Kv;\n\n    p.is_mqa_gqa = is_mqa_gqa;\n\n    TORCH_CHECK(p.B == logsumexp.size(0));\n    TORCH_CHECK(p.Hq == logsumexp.size(1));\n    TORCH_CHECK(p.M == logsumexp.size(2));\n\n    if (scale.has_value()) {\n      p.scale = float(*scale);\n    } else {\n      p.scale = float(1.0 / std::sqrt(float(K)));\n    }\n\n    p.q_ptr = query.data_ptr();\n    p.k_ptr = key.data_ptr();\n    p.v_ptr = value.data_ptr();\n    p.grad_out_ptr = grad_out.data_ptr();\n    p.out_ptr = out.data_ptr();\n\n    p.grad_q_ptr = grad_q.data_ptr();\n    p.grad_k_ptr = is_mqa_gqa ? tmp_grad_k.data_ptr() : grad_k.data_ptr();\n    p.grad_v_ptr = is_mqa_gqa ? tmp_grad_v.data_ptr() : grad_v.data_ptr();\n\n    if (use_grad_q_f32)\n      p.grad_q_f32_ptr = grad_q_f32.data_ptr();\n    else\n      p.grad_q_f32_ptr = nullptr;\n\n    p.q_strides = {\n        static_cast<int>(query.stride(0)),\n        static_cast<int>(query.stride(1)),\n        static_cast<int>(query.stride(2)),\n        static_cast<int>(query.stride(3))};\n    p.k_strides = {\n        static_cast<int>(key.stride(0)),\n        static_cast<int>(key.stride(1)),\n        static_cast<int>(key.stride(2)),\n        static_cast<int>(key.stride(3))};\n    p.v_strides = {\n        static_cast<int>(value.stride(0)),\n        static_cast<int>(value.stride(1)),\n        static_cast<int>(value.stride(2)),\n        static_cast<int>(value.stride(3))};\n    p.out_strides = {\n        static_cast<int>(out.stride(0)),\n        static_cast<int>(out.stride(1)),\n        static_cast<int>(out.stride(2)),\n        static_cast<int>(out.stride(3))};\n    p.grad_out_strides = {\n        static_cast<int>(grad_out.stride(0)),\n        static_cast<int>(grad_out.stride(1)),\n        static_cast<int>(grad_out.stride(2)),\n        static_cast<int>(grad_out.stride(3))};\n\n    p.lsed_strides = {\n        static_cast<int>(logsumexp.stride(0)),\n        static_cast<int>(logsumexp.stride(1)),\n        static_cast<int>(logsumexp.stride(2))};\n\n    if (use_grad_q_f32) {\n      p.grad_q_f32_strides = {\n          static_cast<int>(grad_q_f32.stride(0)),\n          static_cast<int>(grad_q_f32.stride(1)),\n          static_cast<int>(grad_q_f32.stride(2)),\n          static_cast<int>(grad_q_f32.stride(3))};\n    }\n\n    if (is_mqa_gqa) {\n      p.grad_k_strides = {\n          static_cast<int>(tmp_grad_k.stride(0)),\n          static_cast<int>(tmp_grad_k.stride(1)),\n          static_cast<int>(tmp_grad_k.stride(2)),\n          static_cast<int>(tmp_grad_k.stride(3))};\n      p.grad_v_strides = {\n          static_cast<int>(tmp_grad_v.stride(0)),\n          static_cast<int>(tmp_grad_v.stride(1)),\n          static_cast<int>(tmp_grad_v.stride(2)),\n          static_cast<int>(tmp_grad_v.stride(3))};\n    } else {\n      p.grad_k_strides = {\n          static_cast<int>(grad_k.stride(0)),\n          static_cast<int>(grad_k.stride(1)),\n          static_cast<int>(grad_k.stride(2)),\n          static_cast<int>(grad_k.stride(3))};\n      p.grad_v_strides = {\n          static_cast<int>(grad_v.stride(0)),\n          static_cast<int>(grad_v.stride(1)),\n          static_cast<int>(grad_v.stride(2)),\n          static_cast<int>(grad_v.stride(3))};\n    };\n\n    if (bias.has_value()) {\n      CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA((*bias));\n      TORCH_CHECK(bias->scalar_type() == query.scalar_type());\n\n      p.has_attn_bias = true;\n      p.attn_bias_ptr = bias->data_ptr();\n\n      const at::Tensor bias_4d_view = get_bias_4d_view(*bias, B, Hq, M, N);\n\n      p.attn_bias_strides = {\n          static_cast<int>(bias_4d_view.stride(0)),\n          static_cast<int>(bias_4d_view.stride(1)),\n          static_cast<int>(bias_4d_view.stride(2)),\n          static_cast<int>(bias_4d_view.stride(3))};\n\n      if (bias_requires_grad)\n        p.grad_bias_ptr = grad_bias.data_ptr();\n    } else {\n      p.has_attn_bias = false;\n      p.attn_bias_ptr = nullptr;\n      p.grad_bias_ptr = nullptr;\n    }\n\n    p.bias_has_grad = bias_requires_grad;\n\n    p.custom_mask_type = custom_mask_type;\n    p.window_size =\n        window_size.has_value() ? (*window_size > 0 ? *window_size : 0) : 0;\n\n    p.dropout_prob = static_cast<float>(dropout_p);\n    p.philox_seed = rng_seed;\n    p.philox_offset = rng_offset;\n\n    p.logsumexp_ptr = logsumexp.data_ptr();\n    p.dot_out_ptr = dot_out.data_ptr();\n  };\n\n  auto set_grouped_backward_params = [&](GroupedBackwardParams& p) {\n    p.num_batches = seqstart_q->size(0) - 1;\n    p.M = M;\n    p.N = N;\n    p.Hq = Hq;\n    p.Hkv = Hkv;\n    p.K = K;\n    p.Kv = Kv;\n\n    p.is_mqa_gqa = is_mqa_gqa;\n\n    p.max_seqlen_q = *max_seqlen_q_;\n    p.max_seqlen_k = *max_seqlen_k_;\n\n    // unpadded lse layout required\n    TORCH_CHECK(p.Hq == logsumexp.size(1));\n    TORCH_CHECK(p.M == logsumexp.size(2));\n\n    if (scale.has_value())\n      p.scale = float(*scale);\n    else\n      p.scale = float(1.0 / std::sqrt(float(K)));\n\n    p.q_strides = {\n        static_cast<int>(query.stride(1)),\n        static_cast<int>(query.stride(2)),\n        static_cast<int>(query.stride(3))};\n    p.k_strides = {\n        static_cast<int>(key.stride(1)),\n        static_cast<int>(key.stride(2)),\n        static_cast<int>(key.stride(3))};\n    p.v_strides = {\n        static_cast<int>(value.stride(1)),\n        static_cast<int>(value.stride(2)),\n        static_cast<int>(value.stride(3))};\n    p.out_strides = {\n        static_cast<int>(out.stride(1)),\n        static_cast<int>(out.stride(2)),\n        static_cast<int>(out.stride(3))};\n    p.grad_out_strides = {\n        static_cast<int>(grad_out.stride(1)),\n        static_cast<int>(grad_out.stride(2)),\n        static_cast<int>(grad_out.stride(3))};\n\n    p.lsed_strides = {\n        static_cast<int>(logsumexp.stride(1)),\n        static_cast<int>(logsumexp.stride(2))};\n\n    if (use_grad_q_f32) {\n      p.grad_q_f32_strides = {\n          static_cast<int>(grad_q_f32.stride(1)),\n          static_cast<int>(grad_q_f32.stride(2)),\n          static_cast<int>(grad_q_f32.stride(3))};\n    }\n\n    if (is_mqa_gqa) {\n      p.grad_k_strides = {\n          static_cast<int>(tmp_grad_k.stride(1)),\n          static_cast<int>(tmp_grad_k.stride(2)),\n          static_cast<int>(tmp_grad_k.stride(3))};\n      p.grad_v_strides = {\n          static_cast<int>(tmp_grad_v.stride(1)),\n          static_cast<int>(tmp_grad_v.stride(2)),\n          static_cast<int>(tmp_grad_v.stride(3))};\n    } else {\n      p.grad_k_strides = {\n          static_cast<int>(grad_k.stride(1)),\n          static_cast<int>(grad_k.stride(2)),\n          static_cast<int>(grad_k.stride(3))};\n      p.grad_v_strides = {\n          static_cast<int>(grad_v.stride(1)),\n          static_cast<int>(grad_v.stride(2)),\n          static_cast<int>(grad_v.stride(3))};\n    };\n\n    if (bias.has_value()) {\n      CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA((*bias));\n      TORCH_CHECK(bias->scalar_type() == query.scalar_type());\n\n      p.has_attn_bias = true;\n      const at::Tensor bias_4d_view = get_bias_4d_view(*bias, B, Hq, M, N);\n      p.attn_bias_strides = {\n          static_cast<int>(bias_4d_view.stride(0)),\n          static_cast<int>(bias_4d_view.stride(1)),\n          static_cast<int>(bias_4d_view.stride(2)),\n          static_cast<int>(bias_4d_view.stride(3))};\n    } else\n      p.has_attn_bias = false;\n\n    p.bias_has_grad = bias_requires_grad;\n\n    p.custom_mask_type = custom_mask_type;\n    p.window_size =\n        window_size.has_value() ? (*window_size > 0 ? *window_size : 0) : 0;\n\n    // interesting: the tensors have to be defined here, moving to more local\n    // scope will cause issue\n    at::Tensor dev_seqstart_q;\n    at::Tensor dev_seqstart_k;\n    at::Tensor dev_seqlen_k;\n\n    if (seqstart_q->is_cpu()) {\n      dev_seqstart_q = at::empty({p.num_batches + 1}, opts.dtype(at::kInt));\n      p.seqstart_q_dev_ptr = dev_seqstart_q.data_ptr();\n      HIP_CALL_CHECK(hipMemcpyAsync(\n          p.seqstart_q_dev_ptr,\n          seqstart_q->data_ptr(),\n          (p.num_batches + 1) * sizeof(int),\n          hipMemcpyHostToDevice,\n          stream));\n    } else\n      p.seqstart_q_dev_ptr = seqstart_q->data_ptr();\n\n    if (seqstart_k->is_cpu()) {\n      dev_seqstart_k = at::empty({p.num_batches + 1}, opts.dtype(at::kInt));\n\n      p.seqstart_k_dev_ptr = dev_seqstart_k.data_ptr();\n      HIP_CALL_CHECK(hipMemcpyAsync(\n          p.seqstart_k_dev_ptr,\n          seqstart_k->data_ptr(),\n          (p.num_batches + 1) * sizeof(int),\n          hipMemcpyHostToDevice,\n          stream));\n    } else\n      p.seqstart_k_dev_ptr = seqstart_k->data_ptr();\n\n    if (seqlen_k.has_value()) {\n      TORCH_CHECK(seqlen_k->scalar_type() == at::ScalarType::Int);\n      TORCH_CHECK(seqlen_k->dim() == 1);\n      TORCH_CHECK(seqlen_k->size(0) == p.num_batches)\n\n      if (seqlen_k->is_cpu()) {\n        dev_seqlen_k = at::empty({p.num_batches}, opts.dtype(at::kInt));\n\n        p.seqlen_k_dev_ptr = dev_seqlen_k.data_ptr();\n        HIP_CALL_CHECK(hipMemcpyAsync(\n            p.seqlen_k_dev_ptr,\n            seqlen_k->data_ptr(),\n            p.num_batches * sizeof(int),\n            hipMemcpyHostToDevice,\n            stream));\n      } else\n        p.seqlen_k_dev_ptr = seqlen_k->data_ptr();\n    } else\n      p.seqlen_k_dev_ptr = nullptr;\n\n    p.dropout_prob = static_cast<float>(dropout_p);\n    p.philox_seed = rng_seed;\n    p.philox_offset = rng_offset;\n\n    p.q_ptr = query.data_ptr();\n    p.k_ptr = key.data_ptr();\n    p.v_ptr = value.data_ptr();\n\n    p.out_ptr = out.data_ptr();\n    p.grad_out_ptr = grad_out.data_ptr();\n    p.attn_bias_ptr = bias.has_value() ? bias->data_ptr() : nullptr;\n\n    p.logsumexp_ptr = logsumexp.data_ptr();\n    p.dot_out_ptr = dot_out.data_ptr();\n\n    p.grad_q_ptr = grad_q.data_ptr();\n    p.grad_k_ptr = is_mqa_gqa ? tmp_grad_k.data_ptr() : grad_k.data_ptr();\n    p.grad_v_ptr = is_mqa_gqa ? tmp_grad_v.data_ptr() : grad_v.data_ptr();\n    p.grad_bias_ptr = bias_requires_grad ? grad_bias.data_ptr() : nullptr;\n\n    if (use_grad_q_f32)\n      p.grad_q_f32_ptr = grad_q_f32.data_ptr();\n    else\n      p.grad_q_f32_ptr = nullptr;\n  };\n\n  auto inDataType = query.scalar_type();\n\n  if (!seqstart_q.has_value()) { // input is batched\n    BatchedBackwardParams batched_backward_params;\n\n    set_batched_backward_params(batched_backward_params);\n\n    if (inDataType == at::ScalarType::Half) {\n      batched_backward_fp16(batched_backward_params, stream);\n    } else if (inDataType == at::ScalarType::BFloat16) {\n      batched_backward_bf16(batched_backward_params, stream);\n    } else\n      throw std::runtime_error(\"input data-type is not supported\");\n  } else { // input is grouped\n    GroupedBackwardParams grouped_backward_params;\n\n    set_grouped_backward_params(grouped_backward_params);\n\n    if (inDataType == at::ScalarType::Half) {\n      grouped_backward_fp16(grouped_backward_params, stream);\n    } else if (inDataType == at::ScalarType::BFloat16) {\n      grouped_backward_bf16(grouped_backward_params, stream);\n    } else\n      throw std::runtime_error(\"input data-type is not supported\");\n  }\n\n  if (is_mqa_gqa) {\n    auto tmp_grad_k_view = tmp_grad_k.unflatten(2, {Hkv, Hq / Hkv});\n    auto tmp_grad_v_view = tmp_grad_v.unflatten(2, {Hkv, Hq / Hkv});\n    grad_k = tmp_grad_k_view.sum(3);\n    grad_v = tmp_grad_v_view.sum(3);\n  }\n\n  return std::make_tuple(grad_q, grad_k, grad_v, grad_bias);\n}\n\nstd::tuple<at::Tensor, at::Tensor, at::Tensor, at::Tensor>\nefficient_attention_backward_ck_meta(\n    const at::Tensor& grad_out,\n    const at::Tensor& query,\n    const at::Tensor& key,\n    const at::Tensor& value,\n    const c10::optional<at::Tensor>& bias, // additive attention bias\n    // (Mode 1MHK only) [b+1]: cu_seqlens_q[b] contains the\n    // position of the first query token for batch $b\n    const c10::optional<at::Tensor>& seqstart_q,\n    // (Mode 1MHK only) [b+1]: cu_seqlens_k[b] contains the\n    // position of the first key token for batch $b\n    const c10::optional<at::Tensor>& seqstart_k,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_q_,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_k_,\n    const c10::optional<at::Tensor>& seqlen_k,\n    const at::Tensor& logsumexp,\n    const at::Tensor& out,\n    double dropout_p, // dropout probability\n    int64_t rng_seed, // seed using for generating random numbers for dropout\n    int64_t rng_offset, // offset into random number sequence\n    int64_t custom_mask_type,\n    const c10::optional<double> scale,\n    const c10::optional<int64_t> window_size) {\n  int64_t B = query.size(0);\n  int64_t M = query.size(1);\n  int64_t N = key.size(1);\n  int64_t Hq = query.size(2);\n  int64_t Hkv = key.size(2);\n  int64_t K = query.size(3);\n  int64_t Kv = value.size(3);\n\n  auto opts = query.options();\n\n  at::Tensor grad_q, grad_k, grad_v, grad_bias;\n\n  if (query.size(1) == key.size(1) && query.size(3) == value.size(3) &&\n      query.size(2) == key.size(2) &&\n      query.storage().is_alias_of(key.storage()) &&\n      query.storage().is_alias_of(value.storage())) {\n    // Create one big contiguous chunk for grad_q, grad_k, grad_v\n    // This is because q, k and v usually come from a single\n    // output of a linear layer that is chunked.\n    // Creating the gradients with the right layout saves us\n    // a `torch.cat` call in the backward pass\n    at::Tensor chunk = at::empty({B, M, 3, Hq, K}, opts);\n    grad_q = chunk.select(2, 0);\n    grad_k = chunk.select(2, 1);\n    grad_v = chunk.select(2, 2);\n  } else if (\n      key.size(3) == value.size(3) &&\n      key.storage().is_alias_of(value.storage())) {\n    // Create one big contiguous chunk for grad_k, grad_v\n    // This is because k and v usually come from a single\n    // output of a linear layer that is chunked.\n    // Creating the gradients with the right layout saves us\n    // a `torch.cat` call in the backward pass\n    at::Tensor chunk = at::empty({B, N, 2, Hkv, Kv}, opts);\n    grad_k = chunk.select(2, 0);\n    grad_v = chunk.select(2, 1);\n\n    grad_q = at::empty_strided(query.sizes(), query.strides(), query.options());\n  } else {\n    grad_q = at::empty_strided(query.sizes(), query.strides(), query.options());\n    grad_k = at::empty_strided(key.sizes(), key.strides(), key.options());\n    grad_v = at::empty_strided(value.sizes(), value.strides(), value.options());\n  }\n\n  const bool bias_requires_grad = bias.has_value() && bias->requires_grad();\n  // even it is an output, the grad_bias is required to use the same data-type\n  // as bias in CK-FlashAttn\n  if (bias_requires_grad) {\n    grad_bias =\n        at::empty_strided(bias->sizes(), bias->strides(), bias->options());\n  }\n  return std::make_tuple(grad_q, grad_k, grad_v, grad_bias);\n}\n\n} // namespace\n\nTORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::efficient_attention_backward_ck\"),\n      TORCH_FN(efficient_attention_backward_ck));\n}\n\nTORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::efficient_attention_backward_ck\"),\n      TORCH_FN(efficient_attention_backward_ck_meta));\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/attention_ck_rand_uniform.cpp",
    "content": "/*\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n * All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAGeneratorImpl.h>\n#include <c10/core/TensorOptions.h>\n#include <c10/hip/HIPStream.h>\n#include <torch/library.h>\n#include <torch/types.h>\n#include <ATen/cuda/PhiloxUtils.cuh>\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n\n#include \"ck_tiled_rand_uniform_kernel.h\"\n\nnamespace {\n\n/**\n * generate a tensor with random uniform values. only used for testing, not much\n * attention is paid to performance\n */\nat::Tensor rand_uniform_int(\n    double dropout_prob,\n    const at::Tensor& out_pattern) // [Batches, num_head, query_len, key_len]\n{\n  int B = out_pattern.size(0);\n  int num_heads = out_pattern.size(1);\n  int M = out_pattern.size(2);\n  int N = out_pattern.size(3);\n\n  hipStream_t stream = c10::hip::getCurrentHIPStream().stream();\n\n  at::CUDAGeneratorImpl* gen =\n      at::get_generator_or_default<at::CUDAGeneratorImpl>(\n          c10::nullopt, at::cuda::detail::getDefaultCUDAGenerator());\n\n  at::PhiloxCudaState rng_engine_inputs;\n  {\n    std::lock_guard<std::mutex> lock(gen->mutex_);\n    rng_engine_inputs =\n        gen->philox_cuda_state((B + 3) * (num_heads + 1) * (M + 1) * (N + 1));\n  }\n\n  const auto seeds = at::cuda::philox::unpack(rng_engine_inputs);\n\n  int64_t philox_seed = std::get<0>(seeds);\n  int64_t philox_offset = std::get<1>(seeds);\n\n  at::Tensor randvals;\n\n  randvals = at::empty(\n      {B, num_heads, M, N}, out_pattern.options().dtype(at::ScalarType::Byte));\n\n  {\n    // only work for batched mode\n    using FmhaRandUniformKernel_ = FmhaRandUniformKernel<uint8_t, false>;\n\n    const auto kargs = FmhaRandUniformKernel_::MakeKargs(\n        randvals.data_ptr(),\n        M,\n        N,\n        num_heads,\n        B,\n        static_cast<int>(randvals.stride(2)),\n        static_cast<int>(randvals.stride(3)),\n        static_cast<int>(randvals.stride(1)),\n        static_cast<int>(randvals.stride(0)),\n        {philox_seed, philox_offset});\n\n    dim3 kGridSize = FmhaRandUniformKernel_::GridSize(B, num_heads, M, N);\n    constexpr dim3 kBlockSize = FmhaRandUniformKernel_::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaRandUniformKernel_::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaRandUniformKernel_{}, kGridSize, kBlockSize, 0, kargs));\n  }\n\n  (void)hipStreamSynchronize(stream);\n\n  return randvals;\n} // namespace\n\n} // namespace\n\nTORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::_ck_rand_uniform\"),\n      TORCH_FN(rand_uniform_int));\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/attention_forward_generic_ck_tiled.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <cmath>\n#include <mutex>\n#include <tuple>\n\n#include <ATen/Context.h>\n#include <ATen/ScalarOps.h>\n#include <ATen/Tensor.h>\n#include <ATen/core/Generator.h>\n#include <ATen/cuda/CUDAGeneratorImpl.h>\n#include <c10/hip/HIPStream.h>\n#include <c10/util/Optional.h>\n#include <torch/library.h>\n#include <ATen/cuda/PhiloxUtils.cuh>\n\n#include \"ck_fmha_util.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_selector.h\"\n#include \"ck_tiled_fmha_params.h\"\n\nextern void batched_forward_fp16(\n    BatchedForwardParams& param,\n    hipStream_t stream);\nextern void batched_forward_bf16(\n    BatchedForwardParams& param,\n    hipStream_t stream);\nextern void grouped_forward_fp16(\n    GroupedForwardParams& param,\n    hipStream_t stream);\nextern void grouped_forward_bf16(\n    GroupedForwardParams& param,\n    hipStream_t stream);\n\nextern void batched_infer_fp16(BatchedForwardParams& param, hipStream_t stream);\nextern void batched_infer_bf16(BatchedForwardParams& param, hipStream_t stream);\nextern void grouped_infer_fp16(GroupedForwardParams& param, hipStream_t stream);\nextern void grouped_infer_bf16(GroupedForwardParams& param, hipStream_t stream);\n\nnamespace {\n\n/*\n  There are 2 modes for using this function.\n  (Mode BMHK) With all the heads having the same seqlen\n  (Mode 1MHK) `batch=1` with all tokens across batches concatenated\n*/\nstd::tuple<at::Tensor, std::optional<at::Tensor>, int64_t, int64_t>\nefficient_attention_forward_ck(\n    const at::Tensor& query, // [b, seqlen, num_heads_q, K]\n    const at::Tensor& key, // [b, seqlen, num_heads_kv, K]\n    const at::Tensor& value, // [b, seqlen, num_heads_kv, Kv]\n    const c10::optional<at::Tensor>& bias, // [b, num_heads_q, seqlen, seqlen]\n    // (Mode 1MHK only) [b+1]: cu_seqlens_q[b] contains the\n    // position of the first query token for batch $b\n    const c10::optional<at::Tensor>& seqstart_q,\n    // (Mode 1MHK only) [b+1]: cu_seqlen_k[b] contains the\n    // position of the first key token for batch $b\n    const c10::optional<at::Tensor>& seqstart_k,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_q_,\n    double dropout_p, // attention matrix dropout probability\n    bool compute_logsumexp,\n    int64_t custom_mask_type,\n    c10::optional<double> scale,\n    const c10::optional<at::Tensor>& seqlen_k,\n    const c10::optional<int64_t> window_size,\n    const c10::optional<at::Tensor>& block_tables,\n    const c10::optional<int64_t> page_size) {\n  TORCH_CHECK(query.dim() == 4);\n  TORCH_CHECK(key.dim() == 4);\n  TORCH_CHECK(value.dim() == 4);\n\n  // Batch sizes\n  TORCH_CHECK(query.size(0) == key.size(0));\n  TORCH_CHECK(query.size(0) == value.size(0));\n\n  // Sequence length\n  TORCH_CHECK(key.size(1) == value.size(1));\n\n  // Num heads\n  TORCH_CHECK(query.size(2) % key.size(2) == 0);\n  TORCH_CHECK(key.size(2) == value.size(2));\n\n  // Embedding per head\n  TORCH_CHECK(query.size(3) == key.size(3));\n\n  TORCH_CHECK(query.scalar_type() == key.scalar_type());\n  TORCH_CHECK(query.scalar_type() == value.scalar_type());\n\n  TORCH_CHECK(seqstart_q.has_value() == seqstart_k.has_value());\n  if (seqstart_q.has_value()) {\n    TORCH_CHECK(seqstart_q->scalar_type() == at::ScalarType::Int);\n    TORCH_CHECK(seqstart_k->scalar_type() == at::ScalarType::Int);\n    TORCH_CHECK(seqstart_q->dim() == 1 && seqstart_k->dim() == 1);\n    TORCH_CHECK(\n        seqstart_q->size(0) == seqstart_k->size(0) ||\n        seqstart_q->size(0) == seqstart_k->size(0) + 1);\n    TORCH_CHECK(query.size(0) == 1, \"cu_seqlen only supports batch_size=1\");\n    TORCH_CHECK(max_seqlen_q_.has_value());\n    CHECK_NOSPARSE_CONTIGUOUS_CUDA((*seqstart_q));\n    CHECK_NOSPARSE_CONTIGUOUS_CUDA((*seqstart_k));\n  };\n\n  TORCH_CHECK(block_tables.has_value() == page_size.has_value());\n  TORCH_CHECK(!block_tables.has_value() || block_tables->dim() == 2);\n\n  // Currently xformers only use Paged-KVcache in grouped mode\n  TORCH_CHECK(seqstart_q.has_value() || !block_tables.has_value());\n\n  // last dim is contiguous, device is kCUDA\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(query);\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(key);\n  CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(value);\n\n  hipStream_t stream = c10::hip::getCurrentHIPStream().stream();\n\n  int64_t B = query.size(0);\n  int64_t M = query.size(1);\n  int64_t N = key.size(1);\n  int64_t Hq = query.size(-2);\n  int64_t Hkv = key.size(-2);\n  int64_t K = query.size(-1);\n  int64_t Kv = value.size(-1);\n\n  auto opts = query.options();\n\n  std::optional<at::Tensor> logsumexp = std::nullopt;\n\n  at::Tensor out = at::empty({B, M, Hq, Kv}, opts);\n\n  at::Tensor logsumexp_acc;\n  at::Tensor out_acc;\n\n  const bool use_dropout = std::fpclassify(dropout_p) != FP_ZERO;\n  int64_t philox_seed = 0;\n  int64_t philox_offset = 0;\n\n  if (use_dropout) {\n    at::PhiloxCudaState rng_engine_inputs;\n    at::CUDAGeneratorImpl* gen =\n        at::get_generator_or_default<at::CUDAGeneratorImpl>(\n            c10::nullopt, at::cuda::detail::getDefaultCUDAGenerator());\n\n    std::lock_guard<std::mutex> lock(gen->mutex_);\n    // if using dropout, we produce 1 random number for each element of the\n    // attention tensor\n    rng_engine_inputs =\n        gen->philox_cuda_state((B + 3) * (Hq + 1) * (M + 1) * (N + 1));\n\n    const auto seeds = at::cuda::philox::unpack(rng_engine_inputs);\n\n    philox_seed = std::get<0>(seeds);\n    philox_offset = std::get<1>(seeds);\n  }\n\n  auto set_batched_forward_params = [&](BatchedForwardParams& p) {\n    p.B = B;\n    p.M = M;\n    p.N = N;\n    p.Hq = Hq;\n    p.Hkv = Hkv;\n    p.K = K;\n    p.Kv = Kv;\n\n    if (scale.has_value()) {\n      p.scale = float(*scale);\n    } else {\n      p.scale = float(1.0 / std::sqrt(float(K)));\n    }\n\n    p.q_ptr = query.data_ptr();\n    p.k_ptr = key.data_ptr();\n    p.v_ptr = value.data_ptr();\n    p.out_ptr = out.data_ptr();\n\n    p.q_strides = {\n        static_cast<int>(query.stride(0)),\n        static_cast<int>(query.stride(1)),\n        static_cast<int>(query.stride(2)),\n        static_cast<int>(query.stride(3))};\n    p.k_strides = {\n        static_cast<int>(key.stride(0)),\n        static_cast<int>(key.stride(1)),\n        static_cast<int>(key.stride(2)),\n        static_cast<int>(key.stride(3))};\n    p.v_strides = {\n        static_cast<int>(value.stride(0)),\n        static_cast<int>(value.stride(1)),\n        static_cast<int>(value.stride(2)),\n        static_cast<int>(value.stride(3))};\n    p.out_strides = {\n        static_cast<int>(out.stride(0)),\n        static_cast<int>(out.stride(1)),\n        static_cast<int>(out.stride(2)),\n        static_cast<int>(out.stride(3))};\n\n    if (bias.has_value()) {\n      CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA((*bias));\n      TORCH_CHECK(bias->scalar_type() == query.scalar_type());\n\n      p.has_attn_bias = true;\n      p.attn_bias_ptr = bias->data_ptr();\n\n      const at::Tensor bias_4d_view = get_bias_4d_view(*bias, B, Hq, M, N);\n      p.attn_bias_strides = {\n          static_cast<int>(bias_4d_view.stride(0)),\n          static_cast<int>(bias_4d_view.stride(1)),\n          static_cast<int>(bias_4d_view.stride(2)),\n          static_cast<int>(bias_4d_view.stride(3))};\n    } else\n      p.has_attn_bias = false;\n\n    p.custom_mask_type = custom_mask_type;\n    p.window_size =\n        window_size.has_value() ? (*window_size > 0 ? *window_size : 0) : 0;\n\n    p.philox_seed = philox_seed;\n    p.philox_offset = philox_offset;\n    p.compute_logsumexp = compute_logsumexp;\n\n    // the following parameters are only used by training forward\n    if (use_dropout) {\n      p.dropout_prob = static_cast<float>(dropout_p);\n    } else\n      p.dropout_prob = 0.0f;\n\n    if (p.compute_logsumexp) {\n      logsumexp = at::empty({B, Hq, M}, opts.dtype(at::kFloat));\n      p.logsumexp_ptr = logsumexp->data_ptr();\n      p.lse_strides = {\n          static_cast<int>(logsumexp->stride(0)),\n          static_cast<int>(logsumexp->stride(1)),\n          static_cast<int>(logsumexp->stride(2))};\n    } else {\n      p.logsumexp_ptr = nullptr;\n      p.lse_strides = {0, 0, 0};\n    }\n\n    bool use_split_kv;\n    int num_kv_splits;\n\n    std::tie(use_split_kv, num_kv_splits) =\n        get_num_kv_splits_heuristic(p.B, p.Hq, p.M, std::max(p.K, p.Kv), 8);\n\n    // 1) fmha fwd split-kv kernel does not support dropout\n    p.use_split_kv = (!use_dropout && use_split_kv) ? true : false;\n\n    p.num_kv_splits = num_kv_splits;\n\n    if (p.use_split_kv && p.num_kv_splits > 1) {\n      out_acc =\n          at::empty({p.num_kv_splits, B, M, Hq, Kv}, opts.dtype(at::kFloat));\n      p.out_acc_ptr = out_acc.data_ptr();\n      p.out_acc_strides = {\n          static_cast<int>(out_acc.stride(0)),\n          static_cast<int>(out_acc.stride(1)),\n          static_cast<int>(out_acc.stride(2)),\n          static_cast<int>(out_acc.stride(3)),\n          static_cast<int>(out_acc.stride(4))};\n\n      logsumexp_acc =\n          at::empty({p.num_kv_splits, B, Hq, M}, opts.dtype(at::kFloat));\n      p.logsumexp_acc_ptr = logsumexp_acc.data_ptr();\n      p.lse_acc_strides = {\n          static_cast<int>(logsumexp_acc.stride(0)),\n          static_cast<int>(logsumexp_acc.stride(1)),\n          static_cast<int>(logsumexp_acc.stride(2)),\n          static_cast<int>(logsumexp_acc.stride(3))};\n    }\n  };\n\n  auto set_grouped_forward_params = [&](GroupedForwardParams& p) {\n    p.num_batches = seqstart_q->size(0) - 1;\n    p.M = M;\n    p.N = N;\n    p.Hq = Hq;\n    p.Hkv = Hkv;\n    p.K = K;\n    p.Kv = Kv;\n\n    p.max_seqlen_q = *max_seqlen_q_;\n\n    if (scale.has_value()) {\n      p.scale = float(*scale);\n    } else {\n      p.scale = float(1.0 / std::sqrt(float(K)));\n    }\n\n    p.q_ptr = query.data_ptr();\n    p.k_ptr = key.data_ptr();\n    p.v_ptr = value.data_ptr();\n    p.out_ptr = out.data_ptr();\n\n    p.q_strides = {\n        static_cast<int>(query.stride(1)),\n        static_cast<int>(query.stride(2)),\n        static_cast<int>(query.stride(3))};\n    p.k_strides = {\n        static_cast<int>(key.stride(1)),\n        static_cast<int>(key.stride(2)),\n        static_cast<int>(key.stride(3))};\n    p.v_strides = {\n        static_cast<int>(value.stride(1)),\n        static_cast<int>(value.stride(2)),\n        static_cast<int>(value.stride(3))};\n    p.out_strides = {\n        static_cast<int>(out.stride(1)),\n        static_cast<int>(out.stride(2)),\n        static_cast<int>(out.stride(3))};\n\n    if (bias.has_value()) {\n      CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA((*bias));\n      TORCH_CHECK(bias->scalar_type() == query.scalar_type());\n\n      p.has_attn_bias = true;\n      p.attn_bias_ptr = bias->data_ptr();\n\n      const at::Tensor bias_4d_view = get_bias_4d_view(*bias, B, Hq, M, N);\n      p.attn_bias_strides = {\n          static_cast<int>(bias_4d_view.stride(0)),\n          static_cast<int>(bias_4d_view.stride(1)),\n          static_cast<int>(bias_4d_view.stride(2)),\n          static_cast<int>(bias_4d_view.stride(3))};\n    } else\n      p.has_attn_bias = false;\n\n    p.custom_mask_type = custom_mask_type;\n    p.window_size =\n        window_size.has_value() ? (*window_size > 0 ? *window_size : 0) : 0;\n\n    // interesting: the tensors have to be defined here, moving to more local\n    // scope will cause issue\n    at::Tensor dev_seqstart_q;\n    at::Tensor dev_seqstart_k;\n    at::Tensor dev_seqlen_k;\n\n    p.seqstart_q_dev_ptr = seqstart_q->data_ptr();\n    p.seqstart_k_dev_ptr = seqstart_k->data_ptr();\n\n    if (seqlen_k.has_value()) {\n      TORCH_CHECK(seqlen_k->scalar_type() == at::ScalarType::Int);\n      TORCH_CHECK(seqlen_k->dim() == 1);\n      TORCH_CHECK(seqlen_k->size(0) == p.num_batches)\n      CHECK_NOSPARSE_CONTIGUOUS_CUDA((*seqlen_k));\n\n      p.seqlen_k_dev_ptr = seqlen_k->data_ptr();\n    } else\n      p.seqlen_k_dev_ptr = nullptr;\n\n    p.is_gappy = false;\n    if (block_tables.has_value()) {\n      p.block_table_ptr = block_tables->data_ptr();\n      p.page_block_size = *page_size;\n      p.batch_stride_block_table = block_tables->stride(0);\n      p.use_paged_kvcache = true;\n\n      TORCH_CHECK(seqlen_k.has_value());\n\n      // PageBlockDiagonalGappyKeysMask has special way to use seqstart_k,\n      // somehow ck_tile kernel need know this\n      if (seqstart_k->size(0) == seqlen_k->size(0))\n        p.is_gappy = true;\n    } else\n      p.use_paged_kvcache = false;\n\n    p.philox_seed = philox_seed;\n    p.philox_offset = philox_offset;\n    p.compute_logsumexp = compute_logsumexp;\n\n    // the following parameters are only used by training forward\n    if (use_dropout) {\n      p.dropout_prob = static_cast<float>(dropout_p);\n    } else\n      p.dropout_prob = 0.0f;\n\n    if (p.compute_logsumexp) {\n      logsumexp = at::empty({1, Hq, M}, opts.dtype(at::kFloat));\n      p.logsumexp_ptr = logsumexp->data_ptr();\n      p.lse_strides = {\n          static_cast<int>(logsumexp->stride(1)),\n          static_cast<int>(logsumexp->stride(2))};\n    } else {\n      p.logsumexp_ptr = nullptr;\n      p.lse_strides = {0, 0};\n    }\n\n    bool use_split_kv;\n    int num_kv_splits;\n\n    // added for support split_kv\n    std::tie(use_split_kv, num_kv_splits) = get_num_kv_splits_heuristic(\n        p.num_batches, p.Hq, p.max_seqlen_q, std::max(p.K, p.Kv), 8);\n\n    // 1) fmha fwd split-kv kernel does not support dropout\n    // 2) Paged-KVcache is only available from the split-kv kernel at present\n    p.use_split_kv =\n        (p.use_paged_kvcache || (!use_dropout && use_split_kv)) ? true : false;\n\n    p.num_kv_splits = num_kv_splits;\n\n    if (p.use_split_kv && p.num_kv_splits > 1) {\n      out_acc = at::empty({p.num_kv_splits, M, Hq, Kv}, opts.dtype(at::kFloat));\n      p.out_acc_ptr = out_acc.data_ptr();\n      p.out_acc_strides = {\n          static_cast<int>(out_acc.stride(0)),\n          static_cast<int>(out_acc.stride(1)),\n          static_cast<int>(out_acc.stride(2)),\n          static_cast<int>(out_acc.stride(3))};\n\n      logsumexp_acc =\n          at::empty({p.num_kv_splits, 1, Hq, M}, opts.dtype(at::kFloat));\n      p.logsumexp_acc_ptr = logsumexp_acc.data_ptr();\n      p.lse_acc_strides = {\n          static_cast<int>(logsumexp_acc.stride(0)),\n          static_cast<int>(logsumexp_acc.stride(2)),\n          static_cast<int>(logsumexp_acc.stride(3))};\n    }\n  };\n\n  auto inDataType = query.scalar_type();\n\n  if (!seqstart_q.has_value()) { // input is batched\n    BatchedForwardParams batched_forward_params;\n\n    set_batched_forward_params(batched_forward_params);\n\n    if (!batched_forward_params.compute_logsumexp) {\n      if (inDataType == at::ScalarType::Half) {\n        batched_infer_fp16(batched_forward_params, stream);\n      } else if (inDataType == at::ScalarType::BFloat16) {\n        batched_infer_bf16(batched_forward_params, stream);\n      } else\n        throw std::runtime_error(\"input data-type is not supported!\");\n    } else {\n      if (inDataType == at::ScalarType::Half) {\n        batched_forward_fp16(batched_forward_params, stream);\n      } else if (inDataType == at::ScalarType::BFloat16) {\n        batched_forward_bf16(batched_forward_params, stream);\n      } else\n        throw std::runtime_error(\"input data-type is not supported!\");\n    };\n  } else { // input is grouped\n    GroupedForwardParams grouped_forward_params;\n\n    set_grouped_forward_params(grouped_forward_params);\n\n    if (!grouped_forward_params.compute_logsumexp) {\n      if (inDataType == at::ScalarType::Half) {\n        grouped_infer_fp16(grouped_forward_params, stream);\n      } else if (inDataType == at::ScalarType::BFloat16) {\n        grouped_infer_bf16(grouped_forward_params, stream);\n      } else\n        throw std::runtime_error(\"input data-type is not supported!\");\n    } else {\n      if (inDataType == at::ScalarType::Half) {\n        grouped_forward_fp16(grouped_forward_params, stream);\n      } else if (inDataType == at::ScalarType::BFloat16) {\n        grouped_forward_bf16(grouped_forward_params, stream);\n      } else\n        throw std::runtime_error(\"input data-type is not supported!\");\n    };\n  };\n\n  return std::make_tuple(out, logsumexp, philox_seed, philox_offset);\n}\n\n/*\n  There are 2 modes for using this function.\n  (Mode BMHK) With all the heads having the same seqlen\n  (Mode 1MHK) `batch=1` with all tokens across batches concatenated\n*/\nstd::tuple<at::Tensor, std::optional<at::Tensor>, int64_t, int64_t>\nefficient_attention_forward_ck_meta(\n    const at::Tensor& query, // [b, seqlen, num_heads_q, K]\n    const at::Tensor& key, // [b, seqlen, num_heads_kv, K]\n    const at::Tensor& value, // [b, seqlen, num_heads_kv, Kv]\n    const c10::optional<at::Tensor>& bias, // [b, num_heads_q, seqlen, seqlen]\n    // (Mode 1MHK only) [b+1]: cu_seqlens_q[b] contains the\n    // position of the first query token for batch $b\n    const c10::optional<at::Tensor>& seqstart_q,\n    // (Mode 1MHK only) [b+1]: cu_seqlen_k[b] contains the\n    // position of the first key token for batch $b\n    const c10::optional<at::Tensor>& seqstart_k,\n    // (Mode 1MHK only) Maximum sequence length across batches\n    const c10::optional<int64_t> max_seqlen_q_,\n    double dropout_p, // attention matrix dropout probability\n    bool compute_logsumexp,\n    int64_t custom_mask_type,\n    c10::optional<double> scale,\n    const c10::optional<at::Tensor>& seqlen_k,\n    const c10::optional<int64_t> window_size,\n    const c10::optional<at::Tensor>& block_tables,\n    const c10::optional<int64_t> page_size) {\n  at::SymInt B = query.sym_size(0);\n  at::SymInt M = query.sym_size(1);\n  at::SymInt N = key.sym_size(1);\n  at::SymInt Hq = query.sym_size(-2);\n  at::SymInt Hkv = key.sym_size(-2);\n  at::SymInt K = query.sym_size(-1);\n  at::SymInt Kv = value.sym_size(-1);\n  auto opts = query.options();\n  std::optional<at::Tensor> logsumexp = std::nullopt;\n  at::Tensor out = at::empty_symint({B, M, Hq, Kv}, opts);\n  int64_t philox_seed = 0;\n  int64_t philox_offset = 0;\n  if (!seqstart_q.has_value()) { // input is batched\n    if (compute_logsumexp) {\n      logsumexp = at::empty_symint({B, Hq, M}, opts.dtype(at::kFloat));\n    }\n  } else {\n    if (compute_logsumexp) {\n      logsumexp = at::empty_symint({1, Hq, M}, opts.dtype(at::kFloat));\n    }\n  }\n  return std::make_tuple(out, logsumexp, philox_seed, philox_offset);\n}\n\n} // namespace\n\nTORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::efficient_attention_forward_ck\"),\n      TORCH_FN(efficient_attention_forward_ck));\n}\n\nTORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::efficient_attention_forward_ck\"),\n      TORCH_FN(efficient_attention_forward_ck_meta));\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_fmha_test.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <iostream>\n\n#include <torch/library.h>\n\nnamespace {\n\n// For testing xFormers building and binding\nbool is_ck_fmha_available(double val) {\n  std::cout << \"ck fmha is really here, val=\" << val << std::endl;\n  return (true);\n};\n\n} // namespace\n\nTORCH_LIBRARY_FRAGMENT(xformers, m) {\n  m.def(TORCH_SELECTIVE_SCHEMA(\n      \"xformers::is_ck_fmha_available(float val) -> bool\"));\n  m.impl(\n      TORCH_SELECTIVE_NAME(\"xformers::is_ck_fmha_available\"),\n      TORCH_FN(is_ck_fmha_available));\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_fmha_util.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <cstdint>\n#include <iostream>\n#include <sstream>\n#include <stdexcept>\n\n#include <torch/all.h>\n\n#define XFORMERS_CHECK(COND, ERR)          \\\n  if (!(COND)) {                           \\\n    std::ostringstream ostr;               \\\n    ostr << \"'\" #COND \"' failed: \" << ERR; \\\n    throw std::runtime_error(ostr.str());  \\\n  }\n\n#define CHECK_NOSPARSE_CONTIGUOUS_CUDA(TENSOR)                            \\\n  XFORMERS_CHECK(TENSOR.is_cuda(), #TENSOR \" must be a CUDA tensor\");     \\\n  XFORMERS_CHECK(!TENSOR.is_sparse(), #TENSOR \" must be a dense tensor\"); \\\n  XFORMERS_CHECK(TENSOR.is_contiguous(), #TENSOR \" must be contiguous\");\n\n#define CHECK_NOSPARSE_CONTIGUOUS_CPU(TENSOR)                             \\\n  XFORMERS_CHECK(TENSOR.is_cpu(), #TENSOR \" must be a CPU tensor\");       \\\n  XFORMERS_CHECK(!TENSOR.is_sparse(), #TENSOR \" must be a dense tensor\"); \\\n  XFORMERS_CHECK(TENSOR.is_contiguous(), #TENSOR \" must be contiguous\");\n\n#define CHECK_NOSPARSE_LASTCONTIGUOUS_CUDA(TENSOR)                        \\\n  XFORMERS_CHECK(TENSOR.is_cuda(), #TENSOR \" must be a CUDA tensor\");     \\\n  XFORMERS_CHECK(!TENSOR.is_sparse(), #TENSOR \" must be a dense tensor\"); \\\n  XFORMERS_CHECK(                                                         \\\n      TENSOR.stride(-1) == 1, #TENSOR \": last dimension must be contiguous\");\n\n#define HIP_CALL_CHECK(flag)                                                 \\\n  do {                                                                       \\\n    hipError_t _tmpVal;                                                      \\\n    if ((_tmpVal = flag) != hipSuccess) {                                    \\\n      std::ostringstream ostr;                                               \\\n      ostr << \"HIP Function Failed (\" << __FILE__ << \",\" << __LINE__ << \") \" \\\n           << hipGetErrorString(_tmpVal);                                    \\\n      throw std::runtime_error(ostr.str());                                  \\\n    }                                                                        \\\n  } while (0)\n\n/**\n * kernels expect 4D bias/bias.grad with shape\n * (batch_sz, n_heads, n_queries, n_keys). common bias shapes users may pass\n * are:\n * - (n_queries, n_keys)\n * - (batch_sz * n_heads, n_queries, n_keys)\n * - (batch_sz, n_heads, n_queries, n_keys)\n *\n * expand the bias as needed - be careful to only create a view with different\n * shape/strides, no copies allowed.\n */\nstatic inline at::Tensor get_bias_4d_view(\n    const at::Tensor& bias,\n    int batch_sz,\n    int n_heads,\n    int n_queries,\n    int n_keys) {\n  TORCH_CHECK(\n      bias.size(-2) == n_queries,\n      \"bias.size(-2) != n_queries: \",\n      bias.size(-2),\n      \" != \",\n      n_queries);\n  TORCH_CHECK(\n      bias.size(-1) == n_keys,\n      \"bias.size(-1) != n_keys: \",\n      bias.size(-1),\n      \" != \",\n      n_keys);\n  switch (bias.dim()) {\n    case 2: // (n_queries, n_keys) - broadcast across all batches and heads\n      return bias.unsqueeze(0).unsqueeze(0).expand(\n          {batch_sz, n_heads, n_queries, n_keys});\n    case 3: // (batch_sz * n_heads, n_queries, n_keys) - just reshape\n      TORCH_CHECK(bias.size(0) == batch_sz * n_heads);\n      return bias.view({batch_sz, n_heads, n_queries, n_keys});\n    case 4: // (batch_sz, n_heads, n_queries, n_keys) - do nothing\n      TORCH_CHECK(bias.size(0) == batch_sz);\n      TORCH_CHECK(bias.size(1) == n_heads)\n      return bias;\n    default:\n      TORCH_CHECK(false, \"bias can only have ndims in {2, 3, 4}\");\n  }\n}\n\nstatic inline int get_number_of_cu() {\n  int device;\n\n  HIP_CALL_CHECK(hipGetDevice(&device));\n\n  hipDeviceProp_t props;\n\n  HIP_CALL_CHECK(hipGetDeviceProperties(&props, device));\n\n  return props.multiProcessorCount;\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_bool_switch.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\n#pragma once\n\n#define BOOL_SWITCH(COND1, CONST_NAME1, ...) \\\n  [&] {                                      \\\n    if (COND1) {                             \\\n      constexpr bool CONST_NAME1 = true;     \\\n      __VA_ARGS__();                         \\\n    } else {                                 \\\n      constexpr bool CONST_NAME1 = false;    \\\n      __VA_ARGS__();                         \\\n    }                                        \\\n  }()\n\n#define BOOL_SWITCH_2(COND1, CONST_NAME1, COND2, CONST_NAME2, ...) \\\n  [&] {                                                            \\\n    if (COND1) {                                                   \\\n      constexpr bool CONST_NAME1 = true;                           \\\n      BOOL_SWITCH(COND2, CONST_NAME2, ##__VA_ARGS__);              \\\n    } else {                                                       \\\n      constexpr bool CONST_NAME1 = false;                          \\\n      BOOL_SWITCH(COND2, CONST_NAME2, ##__VA_ARGS__);              \\\n    }                                                              \\\n  }()\n\n#define BOOL_SWITCH_3(                                                      \\\n    COND1, CONST_NAME1, COND2, CONST_NAME2, COND3, CONST_NAME3, ...)        \\\n  [&] {                                                                     \\\n    if (COND1) {                                                            \\\n      constexpr bool CONST_NAME1 = true;                                    \\\n      BOOL_SWITCH_2(COND2, CONST_NAME2, COND3, CONST_NAME3, ##__VA_ARGS__); \\\n    } else {                                                                \\\n      constexpr bool CONST_NAME1 = false;                                   \\\n      BOOL_SWITCH_2(COND2, CONST_NAME2, COND3, CONST_NAME3, ##__VA_ARGS__); \\\n    }                                                                       \\\n  }()\n\n#define BOOL_SWITCH_4(                    \\\n    COND1,                                \\\n    CONST_NAME1,                          \\\n    COND2,                                \\\n    CONST_NAME2,                          \\\n    COND3,                                \\\n    CONST_NAME3,                          \\\n    COND4,                                \\\n    CONST_NAME4,                          \\\n    ...)                                  \\\n  [&] {                                   \\\n    if (COND1) {                          \\\n      constexpr bool CONST_NAME1 = true;  \\\n      BOOL_SWITCH_3(                      \\\n          COND2,                          \\\n          CONST_NAME2,                    \\\n          COND3,                          \\\n          CONST_NAME3,                    \\\n          COND4,                          \\\n          CONST_NAME4,                    \\\n          ##__VA_ARGS__);                 \\\n    } else {                              \\\n      constexpr bool CONST_NAME1 = false; \\\n      BOOL_SWITCH_3(                      \\\n          COND2,                          \\\n          CONST_NAME2,                    \\\n          COND3,                          \\\n          CONST_NAME3,                    \\\n          COND4,                          \\\n          CONST_NAME4,                    \\\n          ##__VA_ARGS__);                 \\\n    }                                     \\\n  }()\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_backward.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_bwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasBiasGrad,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nstruct batched_backward_mask_bias_dropout_dispatch {\n  using FmhaBlockDropout =\n      typename FmhaBwdBlockDropoutMaker<kHasDropout, MaxK>::dropout;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaBwdPipelineProblemTemp = ck_tile::BlockFmhaBwdPipelineProblem<\n      typename FmhaBwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::GemmDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::DDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::ODataType,\n      typename FmhaBwdTypeConfig<ScalarType>::OGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::QGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::KGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::VGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::BiasGradDataType,\n      FmhaBwdShape<MaxK>,\n      false, // kIsGroupMode\n      false, // kIsDeterministic\n      FmhaMask,\n      FmhaBlockDropout,\n      FmhaTraits>;\n\n  static constexpr bool NeedConvertGradQ = !std::is_same<\n      typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::QGradDataType>::value;\n\n  static void Run(BatchedBackwardParams& param, hipStream_t stream) {\n    {\n      constexpr ck_tile::index_t kBlockSize = 64;\n\n      const bool pad_seqlen_q = !(param.M % kBlockSize == 0);\n      const bool pad_headdim_v = !(param.Kv % MaxK == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_v, kPadHeadDimV, [&] {\n            constexpr ck_tile::index_t occupancy = 2;\n\n            using FmhaOGradDotOTraits_ = ck_tile::TileFmhaBwdOGradDotOTraits<\n                kPadSeqLenQ,\n                kPadHeadDimV,\n                occupancy>;\n\n            using FmhaBwdOGradDotOPipelineProblem =\n                ck_tile::BlockFmhaBwdOGradDotOPipelineProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::ODataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::OGradDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::DDataType,\n                    kBlockSize,\n                    MaxK, // kVHeaddim\n                    false, // kIsGroupMode\n                    FmhaOGradDotOTraits_>;\n\n            using FmhaBwdOGradDotOPipeline =\n                typename ck_tile::BlockFmhaBwdOGradDotO<\n                    FmhaBwdOGradDotOPipelineProblem>;\n\n            using FmhaBwdOGradDotOKernel_ =\n                ck_tile::FmhaBwdOGradDotOKernel<FmhaBwdOGradDotOPipeline>;\n\n            RunWithBwdOGradDotOKernel<FmhaBwdOGradDotOKernel_>(param, stream);\n          });\n    }\n\n    {\n      constexpr ck_tile::index_t occupancy = 1;\n\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      const bool pad_headdim_q =\n          !(param.K % FmhaBwdShape<MaxK>::kQKHeaddim == 0);\n      const bool pad_headdim_v =\n          !(param.Kv % FmhaBwdShape<MaxK>::kVHeaddim == 0);\n\n      BOOL_SWITCH_2(\n          pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n            using FmhaBwdTraits_ = ck_tile::TileFmhaTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ,\n                kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                kHasBiasGrad,\n                false, // kStoreLSE\n                false, // place-holder for kHasDropout, not used actually\n                false, // kDoFp8StaticQuant place-holder\n                occupancy>;\n\n            using FmhaBwdPipelineProblem =\n                FmhaBwdPipelineProblemTemp<FmhaBwdTraits_, FmhaMask>;\n\n            constexpr auto FmhaBwdPipelineEnum_ =\n                FmhaBwdPipelineEnumSelector<MaxK>::value;\n\n            using FmhaBwdPipeline_ = typename FmhaBwdPipelineMaker<\n                FmhaBwdPipelineEnum_,\n                FmhaBwdPipelineProblem>::pipeline;\n\n            using FmhaBwdKGradEpilogue_ =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::KGradDataType,\n                    kPadSeqLenK,\n                    kPadHeadDimQ>>;\n\n            using FmhaBwdVGradEpilogue_ =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::VGradDataType,\n                    kPadSeqLenK,\n                    kPadHeadDimV>>;\n\n            using FmhaBwdDQDKDVKernel_ = ck_tile::FmhaBwdDQDKDVKernel<\n                FmhaBwdPipeline_,\n                FmhaBwdKGradEpilogue_,\n                FmhaBwdVGradEpilogue_>;\n\n            RunWithBwdDQDKDVKernel<FmhaBwdDQDKDVKernel_>(param, stream);\n          });\n    };\n    if constexpr (NeedConvertGradQ) {\n      constexpr ck_tile::index_t kBlockSize = 256;\n\n      const bool pad_seqlen_q = !(param.M % kBlockSize == 0);\n      const bool pad_headdim_q = !(param.K % MaxK == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_q, kPadHeadDimQ, [&] {\n            constexpr ck_tile::index_t occupancy = 2;\n\n            using FmhaBwdConvertQGradTraits_ =\n                ck_tile::TileFmhaBwdConvertQGradTraits<\n                    kPadSeqLenQ,\n                    kPadHeadDimQ,\n                    occupancy>;\n\n            using FmhaBwdConvertQGradPipelineProblem =\n                ck_tile::BlockFmhaBwdConvertQGradPipelineProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::QGradDataType,\n                    kBlockSize,\n                    FmhaBwdShape<MaxK>::kM0,\n                    FmhaBwdShape<MaxK>::kN0,\n                    MaxK, // kQKHeaddim\n                    false, // kIsGroupMode\n                    false, // kIsDeterministic\n                    FmhaBwdConvertQGradTraits_>;\n\n            using FmhaBwdConvertQGradPipeline =\n                typename ck_tile::BlockFmhaBwdConvertQGrad<\n                    FmhaBwdConvertQGradPipelineProblem>;\n\n            using FmhaBwdConvertQGradKernel_ =\n                ck_tile::FmhaBwdConvertQGradKernel<FmhaBwdConvertQGradPipeline>;\n\n            RunWithBwdConvertQGradKernel<FmhaBwdConvertQGradKernel_>(\n                param, stream);\n          });\n    };\n  }\n\n  template <typename FmhaBwdOGradDotOKernel>\n  static void RunWithBwdOGradDotOKernel(\n      BatchedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdOGradDotOKernel::MakeKargs(\n          param.out_ptr,\n          param.grad_out_ptr,\n          param.dot_out_ptr,\n          1.0f - param.dropout_prob,\n          param.M,\n          param.Kv,\n          param.grad_out_strides[1], // stride_do\n          param.out_strides[1], // stride_o\n          param.grad_out_strides[2], // nhead_stride_do\n          param.out_strides[2], // nhead_stride_o\n          param.lsed_strides[1], // nhead_stride_d\n          param.grad_out_strides[0], // batch_stride_do\n          param.out_strides[0], // batch_stride_o\n          param.lsed_strides[0]); // batch_stride_d\n    }();\n\n    dim3 kGridSize =\n        FmhaBwdOGradDotOKernel::GridSize(param.B, param.Hq, param.M);\n    constexpr dim3 kBlockSize = FmhaBwdOGradDotOKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaBwdOGradDotOKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdOGradDotOKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n\n  template <typename FmhaBwdDQDKDVKernel>\n  static void RunWithBwdDQDKDVKernel(\n      BatchedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdDQDKDVKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          param.logsumexp_ptr,\n          param.grad_out_ptr,\n          param.dot_out_ptr,\n          nullptr, // rand_val_ptr\n          param.grad_k_ptr,\n          param.grad_v_ptr,\n          param.grad_bias_ptr,\n          NeedConvertGradQ ? param.grad_q_f32_ptr : param.grad_q_ptr,\n          param.M, // seqlen_q\n          param.N, // seqlen_k\n          param.K,\n          param.Kv,\n          param.Hq,\n          param.Hq / param.Hkv,\n          param.scale,\n          param.q_strides[1], // q, k, v, bias, do, dq_f32, dk, dv, dbias\n                              // seq-dim stride\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[2],\n          0, // stride_randval\n          param.grad_out_strides[1],\n          NeedConvertGradQ ? param.grad_q_f32_strides[1] : param.q_strides[1],\n          param.grad_k_strides[1],\n          param.grad_v_strides[1],\n          param.attn_bias_strides[2], // assume grad_bias has same strides as\n                                      // bias\n          param.q_strides[2], // q, k, v, bias, do, lse/dot, dq_f32, dk, dv,\n                              // dbias nhead-dim strides\n          param.k_strides[2],\n          param.v_strides[2],\n          param.attn_bias_strides[1],\n          0, // nhead_stride_randval\n          param.grad_out_strides[2],\n          param.lsed_strides[1],\n          NeedConvertGradQ ? param.grad_q_f32_strides[2] : param.q_strides[2],\n          param.grad_k_strides[2],\n          param.grad_v_strides[2],\n          param.attn_bias_strides[1], // assume grad_bias has same strides as\n                                      // bias\n          param.q_strides[0], // q, k, v, bias, do, lse/dot, dk, dv, dbias,\n                              // batch-dim strides\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[0],\n          0, // batch_stride_randval\n          param.grad_out_strides[0],\n          param.lsed_strides[0], // lse/dot is in BHM contiguous layout\n          NeedConvertGradQ ? param.grad_q_f32_strides[0] : param.q_strides[0],\n          param.grad_k_strides[0],\n          param.grad_v_strides[0],\n          param.attn_bias_strides[0], // assume grad_bias has same strides as\n                                      // bias\n          0, // split_stride_dq_acc\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          param.dropout_prob, // dropout ratio\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize = FmhaBwdDQDKDVKernel::GridSize(param.B, param.Hq, param.N);\n    constexpr dim3 kBlockSize = FmhaBwdDQDKDVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaBwdDQDKDVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdDQDKDVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n\n  template <typename FmhaBwdConvertQGradKernel>\n  static void RunWithBwdConvertQGradKernel(\n      BatchedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdConvertQGradKernel::MakeKargs(\n          param.grad_q_f32_ptr,\n          param.grad_q_ptr,\n          param.M, // seqlen_q\n          param.N, // seqlen_k\n          param.K, // headdim of q/k\n          param.q_strides[1],\n          param.grad_q_f32_strides[1],\n          param.q_strides[2],\n          param.grad_q_f32_strides[2],\n          param.q_strides[0],\n          param.grad_q_f32_strides[0],\n          0);\n    }();\n\n    dim3 kGridSize =\n        FmhaBwdConvertQGradKernel::GridSize(param.B, param.Hq, param.M);\n    constexpr dim3 kBlockSize = FmhaBwdConvertQGradKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaBwdConvertQGradKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdConvertQGradKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n};\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasBiasGrad,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_batched_backward_mask_bias_dropout_dispatch(\n    BatchedBackwardParams& param,\n    hipStream_t stream) {\n  batched_backward_mask_bias_dropout_dispatch<\n      ScalarType,\n      kHasMask,\n      kHasBias,\n      kHasBiasGrad,\n      kHasDropout,\n      MaxK>::Run(param, stream);\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_backward_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_backward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_batched_backward_bf16_instances_ref.h\"\n\nvoid batched_backward_bf16(BatchedBackwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_3(\n      param.has_attn_bias,\n      kHasBias,\n      param.bias_has_grad,\n      kHasBiasGrad,\n      has_dropout,\n      kHasDropout,\n      [&] {\n        if constexpr (kHasBias || !kHasBiasGrad) {\n          FMHA_BWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n            if (param.custom_mask_type == 0 && param.window_size <= 0)\n              run_batched_backward_mask_bias_dropout_dispatch<\n                  ck_tile::bf16_t,\n                  false,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else if (\n                param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n                param.window_size > 0)\n              run_batched_backward_mask_bias_dropout_dispatch<\n                  ck_tile::bf16_t,\n                  true,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else\n              throw std::runtime_error(\"Invalid custom_mask_type value\");\n          });\n        } else\n          throw std::runtime_error(\n              \"bias_has_grad should be false when has_attn_bias is false!\");\n      });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_backward_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_backward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_batched_backward_fp16_instances_ref.h\"\n\nvoid batched_backward_fp16(BatchedBackwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_3(\n      param.has_attn_bias,\n      kHasBias,\n      param.bias_has_grad,\n      kHasBiasGrad,\n      has_dropout,\n      kHasDropout,\n      [&] {\n        if constexpr (kHasBias || !kHasBiasGrad) {\n          FMHA_BWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n            if (param.custom_mask_type == 0 && param.window_size <= 0)\n              run_batched_backward_mask_bias_dropout_dispatch<\n                  ck_tile::fp16_t,\n                  false,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else if (\n                param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n                param.window_size > 0)\n              run_batched_backward_mask_bias_dropout_dispatch<\n                  ck_tile::fp16_t,\n                  true,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else\n              throw std::runtime_error(\"Invalid custom_mask_type value\");\n          });\n        } else\n          throw std::runtime_error(\n              \"bias_has_grad should be false when has_attn_bias is false!\");\n      });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <algorithm>\n#include \"ck_tiled_fmha_batched_forward_dispatch.h\"\n#include \"ck_tiled_fmha_batched_forward_splitkv_dispatch.h\"\n#include \"ck_tiled_fmha_batched_forward_splitkv_smallq_dispatch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_selector.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_batched_forward_mask_bias_dropout_dispatch(\n    BatchedForwardParams& param,\n    hipStream_t stream) {\n  // currently split-kv implementation does not support:\n  // (*) dropout\n  // (*) head dimension > 256\n  if constexpr (!kHasDropout) {\n    if (param.use_split_kv && MaxK <= 256) {\n      if constexpr (MaxK <= 256) {\n        if (use_splitkv_smallq(param.M, std::max(param.K, param.Kv))) {\n          batched_forward_splitkv_smallq_mask_bias_dropout_dispatch<\n              ScalarType,\n              kHasMask,\n              kHasBias,\n              MaxK>::Run(param, stream);\n        } else {\n          FMHA_FWD_SEQLEN_Q_SWITCH(param.M, MaxSeqlenQ, [&] {\n            batched_forward_splitkv_mask_bias_dropout_dispatch<\n                ScalarType,\n                kHasMask,\n                kHasBias,\n                MaxK,\n                MaxSeqlenQ>::Run(param, stream);\n          });\n        }\n      } else {\n        // Unreachable. Do not instantiate split-kv pipelines with head\n        // dimension > 256\n      }\n    } else {\n      if (get_fmha_fwd_mtile(param.B, param.Hq, param.M) == 128)\n        batched_forward_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            128>::Run(param, stream);\n      else\n        batched_forward_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            64>::Run(param, stream);\n    }\n  } else {\n    // at present, dropout of fwd kernel requires 32x32 WarpTile\n    batched_forward_mask_bias_dropout_dispatch<\n        ScalarType,\n        kHasMask,\n        kHasBias,\n        kHasDropout,\n        MaxK,\n        128>::Run(param, stream);\n  }\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_forward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_batched_forward_bf16_instances_ref.h\"\n\nvoid batched_forward_bf16(BatchedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_batched_forward_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_batched_forward_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MTile>\nstruct batched_forward_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaPipelineProblemTemp = ck_tile::BlockFmhaPipelineProblem<\n      typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n      typename FmhaFwdShape<MaxK, MTile>::Type,\n      false, // kIsGroupMode\n      AttentionVariant<FmhaTraits>,\n      FmhaMask,\n      FmhaTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n    using FmhaFwdShape_ = typename FmhaFwdShape<MaxK, MTile>::Type;\n    constexpr ck_tile::index_t occupancy =\n        (MaxK == 64) ? 3 : ((MaxK >= 256) ? 1 : 2);\n\n    constexpr auto kBiasEnum = kHasBias\n        ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n        : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n    const bool pad_seqlen_q = !(param.M % FmhaFwdShape_::kM0 == 0);\n    const bool pad_seqlen_k =\n        (param.N == 0) || !(param.N % FmhaFwdShape_::kN0 == 0);\n    const bool pad_headdim_q = !(param.K % FmhaFwdShape_::kSubQKHeaddim == 0);\n    const bool pad_headdim_v = !(param.Kv % FmhaFwdShape_::kN1 == 0);\n\n    // usually headdim_q and headdim_v are same, consider them together to\n    // determine whether to do padding saving some compiling time\n    const bool pad_headdim = (pad_headdim_q || pad_headdim_v);\n\n    const bool use_async_pipeline =\n        ((param.K % 8 == 0) && (param.Kv % 8 == 0) && (MaxK <= 128));\n\n    BOOL_SWITCH_3(\n        pad_seqlen_q,\n        kPadSeqLenQ,\n        pad_seqlen_k,\n        kPadSeqLenK,\n        pad_headdim,\n        kPadHeadDim,\n        [&] {\n          using FmhaFwdTraits_ = ck_tile::TileFmhaTraits<\n              kPadSeqLenQ,\n              kPadSeqLenK,\n              kPadHeadDim, // kPadHeadDimQ\n              kPadHeadDim, // kPadHeadDimV\n              false, // kHasLogitsSoftCap\n              kBiasEnum,\n              false, // kHasBiasGrad place-holder\n              true, // kStoreLSE\n              kHasDropout,\n              false, // kDoFp8StaticQuant place-holder\n              occupancy>;\n\n          using FmhaPipelineProblem =\n              FmhaPipelineProblemTemp<FmhaFwdTraits_, FmhaMask>;\n\n          using FmhaFwdPipeline_ = std::conditional_t<\n              MaxK <= 256,\n              ck_tile::BlockFmhaPipelineQRKSVS<FmhaPipelineProblem>,\n              ck_tile::BlockFmhaPipelineQSKSVS<FmhaPipelineProblem>>;\n\n          using FmhaFwdEpilogue_ =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDim>>;\n\n          using FmhaFwdKernel_ =\n              ck_tile::FmhaFwdKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n          RunWithKernel<FmhaFwdKernel_>(param, stream);\n        });\n  };\n\n  template <typename FmhaFwdKernel>\n  static void RunWithKernel(BatchedForwardParams& param, hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaFwdKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          nullptr, // rand_val_ptr\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.M, // seqlen_q\n          param.N, // seqlen_k\n          param.K, // hdim_q\n          param.Kv, // hdim_v\n          param.Hq, // nhead_q\n          param.Hq / param.Hkv, // nhead_ratio_qk\n          param.scale,\n          1.0f, // scale_p\n          1.0f, // scale_o\n          0.0f, // logits_soft_cap\n          param.q_strides[1], // q, k, v, bias, randval, out tensor seq-dim\n                              // stride\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[2],\n          0, // stride_randval\n          param.out_strides[1],\n          param.q_strides[2], // q, k, v, bias, randval, lse, out tensor\n                              // head-dim stride\n          param.k_strides[2],\n          param.v_strides[2],\n          param.attn_bias_strides[1],\n          0, // nhead_randva\n          param.lse_strides[1], // nhead_stride_lse\n          param.out_strides[2],\n          param.q_strides[0], // q, k, v, bias, randval, lse, out tensor\n                              // batch-dim stride\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[0],\n          0, // batch_stride_randval\n          param.lse_strides[0], // batch_stride_lse\n          param.out_strides[0],\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          param.dropout_prob, // dropout ratio\n          false, // is_store_randval\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize =\n        FmhaFwdKernel::GridSize(param.B, param.Hq, param.M, param.Kv, false);\n    constexpr dim3 kBlockSize = FmhaFwdKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_forward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_batched_forward_fp16_instances_ref.h\"\n\nvoid batched_forward_fp16(BatchedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_batched_forward_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_batched_forward_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward_splitkv_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MaxSeqlenQ>\nstruct batched_forward_splitkv_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type,\n          false, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          false, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      const bool pad_seqlen_q = !(param.M % FmhaTileShape::kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n\n      // usually headdim_q and headdim_v are same, consider them together to\n      // determine whether to do padding saving some compiling time\n      const bool pad_headdim = (pad_headdim_q || pad_headdim_v);\n\n      const bool has_uneven_splits =\n          !(param.N % (param.num_kv_splits * FmhaTileShape::kN0) == 0);\n\n      BOOL_SWITCH_3(\n          pad_seqlen_q,\n          kPadSeqLenQ,\n          pad_headdim,\n          kPadHeadDim,\n          has_uneven_splits,\n          kHasUnevenSplits,\n          [&] {\n            constexpr bool kPadSeqLenK = kHasUnevenSplits ? true : false;\n\n            using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDim, // kPadHeadDimQ,\n                kPadHeadDim, // kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                true, // kStoreLSE\n                false, // kDoFp8StaticQuant place-holder\n                false, // kIsPagedKV\n                kHasUnevenSplits,\n                false, // kMergeNumHeadGroupsSeqLenQ\n                occupancy>;\n\n            if (param.num_kv_splits > 1) {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            } else {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            }\n          });\n    }\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      const bool pad_seqlen_q = !(param.M % kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_v, kPadHeadDimV, [&] {\n            FMHA_FWD_NUM_KV_SPLITS_SWITCH(\n                param.num_kv_splits, kLogMaxSplits, [&] {\n                  using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n                      kPadSeqLenQ,\n                      kPadHeadDimV,\n                      true, // kStoreLSE\n                      false, // kDoFp8StaticQuant place-holder\n                      kLogMaxSplits,\n                      -1>;\n\n                  using FmhaPipelineProblem =\n                      FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n                  using FmhaPipeline =\n                      ck_tile::BlockFmhaFwdSplitKVCombinePipeline<\n                          FmhaPipelineProblem>;\n\n                  using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                      ck_tile::Default2DEpilogueProblem<\n                          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                          kPadSeqLenQ,\n                          kPadHeadDimV>>;\n\n                  using FmhaKernel = ck_tile::\n                      FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n                  RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n                });\n          });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap, not used\n            param.q_strides[1], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[2],\n            param.q_strides[2], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[2],\n            param.out_acc_strides[3],\n            param.q_strides[0], // q, k, v, bias, lse_acc, out_acc tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[1],\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_ptr,\n            param.out_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_strides[1],\n            param.q_strides[2], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_strides[1],\n            param.out_strides[2],\n            param.q_strides[0], // q, k, v, bias, lse, out tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_strides[0],\n            param.out_strides[0],\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.B, param.Hq, param.Hkv, param.M, param.Kv, param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.B, // batches\n          param.M, // seqlen_q\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[2], // row_stride_o_acc\n          param.out_strides[1], // row_stride_o\n          param.lse_acc_strides[2], // head_stride_lse_acc\n          param.out_acc_strides[3], // head_stride_o_acc\n          param.lse_strides[1], // head_stride_lse\n          param.out_strides[2], // head_stride_o\n          param.lse_acc_strides[1], // batch_stride_lse_acc\n          param.out_acc_strides[1], // batch_stride_o_acc\n          param.lse_strides[0], // batch_stride_lse\n          param.out_strides[0], // batch_stride_o\n          param.lse_acc_strides[0], // split_stride_lse_acc\n          param.out_acc_strides[0]); // split_stride_out_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.B, param.Hq, param.M, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_forward_splitkv_smallq_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK>\nstruct batched_forward_splitkv_smallq_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVSmallQShape<MaxK>::Type,\n          false, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          false, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      const bool pad_seqlen_q = !(param.M % FmhaTileShape::kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n\n      // usually headdim_q and headdim_v are same, consider them together to\n      // determine whether to do padding saving some compiling time\n      const bool pad_headdim = (pad_headdim_q || pad_headdim_v);\n\n      const bool has_uneven_splits =\n          !(param.N % (param.num_kv_splits * FmhaTileShape::kN0) == 0);\n\n      BOOL_SWITCH_3(\n          pad_seqlen_q,\n          kPadSeqLenQ,\n          pad_headdim,\n          kPadHeadDim,\n          has_uneven_splits,\n          kHasUnevenSplits,\n          [&] {\n            constexpr bool kPadSeqLenK = kHasUnevenSplits ? true : false;\n\n            using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDim, // kPadHeadDimQ,\n                kPadHeadDim, // kPadHeadDimV,\n                false, // kHasSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                true, // kStoreLSE\n                false, // kDoFp8StaticQuant place-holder\n                false, // kIsPagedKV\n                kHasUnevenSplits,\n                false, // kMergeNumHeadGroupsSeqLenQ\n                occupancy>;\n\n            if (param.num_kv_splits > 1) {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            } else {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            }\n          });\n    }\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      const bool pad_seqlen_q = !(param.M % kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_v, kPadHeadDimV, [&] {\n            FMHA_FWD_NUM_KV_SPLITS_SWITCH(\n                param.num_kv_splits, kLogMaxSplits, [&] {\n                  using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n                      kPadSeqLenQ,\n                      kPadHeadDimV,\n                      true, // kStoreLSE\n                      false, // kDoFp8StaticQuant place-holder\n                      kLogMaxSplits,\n                      -1>;\n\n                  using FmhaPipelineProblem =\n                      FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n                  using FmhaPipeline =\n                      ck_tile::BlockFmhaFwdSplitKVCombinePipeline<\n                          FmhaPipelineProblem>;\n\n                  using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                      ck_tile::Default2DEpilogueProblem<\n                          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                          kPadSeqLenQ,\n                          kPadHeadDimV>>;\n\n                  using FmhaKernel = ck_tile::\n                      FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n                  RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n                });\n          });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[2],\n            param.q_strides[2], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[2],\n            param.out_acc_strides[3],\n            param.q_strides[0], // q, k, v, bias, lse_acc, out_acc tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[1],\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_ptr,\n            param.out_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_strides[1],\n            param.q_strides[2], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_strides[1],\n            param.out_strides[2],\n            param.q_strides[0], // q, k, v, bias, lse, out tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_strides[0],\n            param.out_strides[0],\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.B, param.Hq, param.Hkv, param.M, param.Kv, param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.B, // batches\n          param.M, // seqlen_q\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[2], // row_stride_o_acc\n          param.out_strides[1], // row_stride_o\n          param.lse_acc_strides[2], // head_stride_lse_acc\n          param.out_acc_strides[3], // head_stride_o_acc\n          param.lse_strides[1], // head_stride_lse\n          param.out_strides[2], // head_stride_o\n          param.lse_acc_strides[1], // batch_stride_lse_acc\n          param.out_acc_strides[1], // batch_stride_o_acc\n          param.lse_strides[0], // batch_stride_lse\n          param.out_strides[0], // batch_stride_o\n          param.lse_acc_strides[0], // split_stride_lse_acc\n          param.out_acc_strides[0]); // split_stride_out_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.B, param.Hq, param.M, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <algorithm>\n#include \"ck_tiled_fmha_batched_infer_dispatch.h\"\n#include \"ck_tiled_fmha_batched_infer_splitkv_dispatch.h\"\n#include \"ck_tiled_fmha_batched_infer_splitkv_smallq_dispatch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_selector.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_batched_infer_mask_bias_dropout_dispatch(\n    BatchedForwardParams& param,\n    hipStream_t stream) {\n  // currently split-kv implementation does not support:\n  // (*) dropout\n  // (*) head dimension > 256\n  if constexpr (!kHasDropout) {\n    if (param.use_split_kv && MaxK <= 256) {\n      if constexpr (MaxK <= 256) {\n        if (use_splitkv_smallq(param.M, std::max(param.K, param.Kv))) {\n          batched_infer_splitkv_smallq_mask_bias_dropout_dispatch<\n              ScalarType,\n              kHasMask,\n              kHasBias,\n              MaxK>::Run(param, stream);\n        } else {\n          FMHA_FWD_SEQLEN_Q_SWITCH(param.M, MaxSeqlenQ, [&] {\n            batched_infer_splitkv_mask_bias_dropout_dispatch<\n                ScalarType,\n                kHasMask,\n                kHasBias,\n                MaxK,\n                MaxSeqlenQ>::Run(param, stream);\n          });\n        }\n      } else {\n        // Unreachable. Do not instantiate split-kv pipelines with head\n        // dimension > 256\n      }\n    } else {\n      const auto mtile = get_fmha_fwd_mtile(param.B, param.Hq, param.M);\n\n      if (mtile == 128)\n        batched_infer_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            128>::Run(param, stream);\n      else\n        batched_infer_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            64>::Run(param, stream);\n    }\n  } else {\n    // at present, dropout of fwd kernel requires 32x32 WarpTile\n    batched_infer_mask_bias_dropout_dispatch<\n        ScalarType,\n        kHasMask,\n        kHasBias,\n        kHasDropout,\n        MaxK,\n        128>::Run(param, stream);\n  }\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_infer.h\"\n\n#include \"instances/fmha_batched_infer_bf16_instances_ref.h\"\n\nvoid batched_infer_bf16(BatchedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_batched_infer_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_batched_infer_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MTile>\nstruct batched_infer_mask_bias_dropout_dispatch {\n  static constexpr bool kUseWholeKPrefetchPipeline =\n      (MaxK <= 128 && !kHasDropout);\n\n  using FmhaShape = typename FmhaFwdShape<MaxK, MTile>::Type;\n\n  static constexpr ck_tile::index_t kKLoadLength =\n      (kUseWholeKPrefetchPipeline || MaxK > 256) ? FmhaShape::kQKHeaddim\n                                                 : FmhaShape::kSubQKHeaddim;\n\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaPipelineProblemTemp = ck_tile::BlockFmhaPipelineProblem<\n      typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n      FmhaShape,\n      false, // kIsGroupMode\n      AttentionVariant<FmhaTraits>,\n      FmhaMask,\n      FmhaTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n    constexpr ck_tile::index_t occupancy = -1;\n\n    constexpr auto kBiasEnum = kHasBias\n        ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n        : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n    const bool pad_seqlen_k = !(param.N % FmhaShape::kN0 == 0);\n    const bool pad_headdim_q = !(param.K % kKLoadLength == 0);\n    const bool pad_headdim_v = !(param.Kv % FmhaShape::kN1 == 0);\n\n    // no need to check seqlen_q since it is not used as fastest dim,\n    // buffer_load_dwordxx/buffer_store_dwordxx can handle oob access\n    constexpr bool kPadSeqLenQ = false;\n\n    // only use qr_ks_vs_async pipeline with hdim-96\n    const bool use_async_pipeline =\n        (!kHasBias && (param.K % 8 == 0) && (param.Kv % 8 == 0) &&\n         (MaxK <= 128 && MTile == 128));\n\n    if (!use_async_pipeline) {\n      BOOL_SWITCH_3(\n          pad_seqlen_k,\n          kPadSeqLenK,\n          pad_headdim_q,\n          kPadHeadDimQ,\n          pad_headdim_v,\n          kPadHeadDimV,\n          [&] {\n            using FmhaTraits = ck_tile::TileFmhaTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ, // kPadHeadDimQ,\n                kPadHeadDimV, // kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                false, // kStoreLSE\n                kHasDropout,\n                false, // kDoFp8StaticQuant place-holder\n                occupancy>;\n\n            using FmhaPipelineProblem =\n                FmhaPipelineProblemTemp<FmhaTraits, FmhaMask>;\n\n            using FmhaEpilogue =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                    kPadSeqLenQ,\n                    kPadHeadDimV>>;\n\n            if constexpr (kUseWholeKPrefetchPipeline) {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQRKSVSWholeKPrefetch<\n                      FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            } else if constexpr (MaxK <= 256) {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQRKSVS<FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            } else {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQSKSVS<FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            }\n          });\n    } else {\n      BOOL_SWITCH(pad_seqlen_k, kPadSeqLenK, [&] {\n        if constexpr (MaxK <= 128 && MTile == 128) {\n          using FmhaTraits = ck_tile::TileFmhaTraits<\n              true, // kPadSeqLenQ,\n              kPadSeqLenK,\n              true, // kPadHeadDimQ,\n              true, // kPadHeadDimV,\n              false, // kHasLogitsSoftCap\n              kBiasEnum,\n              false, // kHasBiasGrad place-holder\n              false, // kStoreLSE\n              kHasDropout,\n              false, // kDoFp8StaticQuant place-holder\n              occupancy>;\n\n          using FmhaPipelineProblem =\n              FmhaPipelineProblemTemp<FmhaTraits, FmhaMask>;\n\n          using FmhaPipeline =\n              ck_tile::BlockFmhaPipelineQRKSVSAsync<FmhaPipelineProblem>;\n\n          using FmhaEpilogue =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  true,\n                  true>>;\n\n          using FmhaKernel = ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n          RunWithKernel<FmhaKernel>(param, stream);\n        } else {\n          /* runtime will never get here, so no codes to compile */\n        };\n      });\n    };\n  };\n\n  template <typename FmhaKernel>\n  static void RunWithKernel(BatchedForwardParams& param, hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          nullptr, // rand_val_ptr\n          nullptr, // lse_ptr\n          param.out_ptr,\n          param.M, // seqlen_q\n          param.N, // seqlen_k\n          param.K, // hdim_q\n          param.Kv, // hdim_v\n          param.Hq, // nhead_q\n          param.Hq / param.Hkv, // nhead_ratio_qk\n          param.scale,\n          1.0f, // scale_p\n          1.0f, // scale_o\n          0.0f, // logits_soft_cap\n          param.q_strides[1], // q, k, v, bias, randval, out tensor seq-dim\n                              // stride\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[2],\n          0, // stride_randval\n          param.out_strides[1],\n          param.q_strides[2], // q, k, v, bias, randval, lse, out tensor\n                              // head-dim stride\n          param.k_strides[2],\n          param.v_strides[2],\n          param.attn_bias_strides[1],\n          0, // nhead_stride_randval\n          0, // nhead_stride_lse\n          param.out_strides[2],\n          param.q_strides[0], // q, k, v, bias, randval, lse, out tensor\n                              // batch-dim stride\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[0],\n          0, // batch_stride_randval\n          0, // batch_stride_lse\n          param.out_strides[0],\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          param.dropout_prob, // dropout ratio\n          false, // is_store_randval\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize =\n        FmhaKernel::GridSize(param.B, param.Hq, param.M, param.Kv, false);\n    constexpr dim3 kBlockSize = FmhaKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_batched_infer.h\"\n\n#include \"instances/fmha_batched_infer_fp16_instances_ref.h\"\n\nvoid batched_infer_fp16(BatchedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_batched_infer_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_batched_infer_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer_splitkv_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MaxSeqlenQ>\nstruct batched_infer_splitkv_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type,\n          false, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          false, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      const bool pad_seqlen_q = !(param.M % FmhaTileShape::kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n\n      // usually headdim_q and headdim_v are same, consider them together to\n      // determine whether to do padding saving some compiling time\n      const bool pad_headdim = (pad_headdim_q || pad_headdim_v);\n\n      const bool has_uneven_splits =\n          !(param.N % (param.num_kv_splits * FmhaTileShape::kN0) == 0);\n\n      BOOL_SWITCH_3(\n          pad_seqlen_q,\n          kPadSeqLenQ,\n          pad_headdim,\n          kPadHeadDim,\n          has_uneven_splits,\n          kHasUnevenSplits,\n          [&] {\n            constexpr bool kPadSeqLenK = kHasUnevenSplits ? true : false;\n\n            if (param.num_kv_splits > 1) {\n              using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                  kPadSeqLenQ,\n                  kPadSeqLenK,\n                  kPadHeadDim, // kPadHeadDimQ,\n                  kPadHeadDim, // kPadHeadDimV,\n                  false, // kHasLogitsSoftCap\n                  kBiasEnum,\n                  false, // kHasBiasGrad place-holder\n                  true, // kStoreLSE\n                  false, // kDoFp8StaticQuant place-holder\n                  false, // kIsPagedKV\n                  kHasUnevenSplits,\n                  false, // kMergeNumHeadGroupsSeqLenQ\n                  occupancy>;\n\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            } else {\n              using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                  kPadSeqLenQ,\n                  kPadSeqLenK,\n                  kPadHeadDim, // kPadHeadDimQ,\n                  kPadHeadDim, // kPadHeadDimV,\n                  false, // kHasLogitsSoftCap\n                  kBiasEnum,\n                  false, // kHasBiasGrad place-holder\n                  false, // kStoreLSE\n                  false, // kDoFp8StaticQuant place-holder\n                  false, // kIsPagedKV\n                  kHasUnevenSplits,\n                  false, // kMergeNumHeadGroupsSeqLenQ\n                  occupancy>;\n\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            }\n          });\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      const bool pad_seqlen_q = !(param.M % kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_v, kPadHeadDimV, [&] {\n            FMHA_FWD_NUM_KV_SPLITS_SWITCH(\n                param.num_kv_splits, kLogMaxSplits, [&] {\n                  using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n                      kPadSeqLenQ,\n                      kPadHeadDimV,\n                      false, // kStoreLSE\n                      false, // kDoFp8StaticQuant place-holder\n                      kLogMaxSplits,\n                      -1>;\n\n                  using FmhaPipelineProblem =\n                      FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n                  using FmhaPipeline =\n                      ck_tile::BlockFmhaFwdSplitKVCombinePipeline<\n                          FmhaPipelineProblem>;\n\n                  using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                      ck_tile::Default2DEpilogueProblem<\n                          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                          kPadSeqLenQ,\n                          kPadHeadDimV>>;\n\n                  using FmhaKernel = ck_tile::\n                      FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n                  RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n                });\n          });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[2],\n            param.q_strides[2], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[2],\n            param.out_acc_strides[3],\n            param.q_strides[0], // q, k, v, bias, lse_acc, out_acc tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[1],\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            nullptr, // lse_ptr\n            param.out_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_strides[1],\n            param.q_strides[2], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            0, // nhead_stride_lse\n            param.out_strides[2],\n            param.q_strides[0], // q, k, v, bias, lse, out tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            0, // batch_stride_lse\n            param.out_strides[0],\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.B, param.Hq, param.Hkv, param.M, param.Kv, param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          nullptr, // lse_ptr, not used\n          param.out_ptr,\n          param.B, // batches\n          param.M, // seqlen_q\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[2], // row_stride_o_acc\n          param.out_strides[1], // row_stride_o\n          param.lse_acc_strides[2], // head_stride_lse_acc\n          param.out_acc_strides[3], // head_stride_o_acc\n          0, // head_stride_lse, // not used\n          param.out_strides[2], // head_stride_o\n          param.lse_acc_strides[1], // batch_stride_lse_acc\n          param.out_acc_strides[1], // batch_stride_o_acc\n          0, // batch_stride_lse, not used\n          param.out_strides[0], // batch_stride_o\n          param.lse_acc_strides[0], // split_stride_lse_acc\n          param.out_acc_strides[0]); // split_stride_out_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.B, param.Hq, param.M, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_batched_infer_splitkv_smallq_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK>\nstruct batched_infer_splitkv_smallq_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVSmallQShape<MaxK>::Type,\n          false, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          false, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(BatchedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      const bool pad_seqlen_q = !(param.M % FmhaTileShape::kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n\n      // usually headdim_q and headdim_v are same, consider them together to\n      // determine whether to do padding saving some compiling time\n      const bool pad_headdim = (pad_headdim_q || pad_headdim_v);\n\n      const bool has_uneven_splits =\n          !(param.N % (param.num_kv_splits * FmhaTileShape::kN0) == 0);\n\n      // indicates to the splitkv kernel whether should it merge Hq/Hkv with\n      // seqlen_q\n      const bool merge_nhead_groups_seqlen_q =\n          ((param.M == 1) && (param.Hq > param.Hkv) && !kHasBias && !kHasMask);\n\n      if (merge_nhead_groups_seqlen_q) {\n        using FmhaMaskNone = ck_tile::SimplifiedGenericAttentionMask<false>;\n        BOOL_SWITCH_2(\n            pad_headdim, kPadHeadDim, has_uneven_splits, kHasUnevenSplits, [&] {\n              constexpr bool kPadSeqLenK = kHasUnevenSplits ? true : false;\n\n              if (param.num_kv_splits > 1) {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    true, // kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDim, // kPadHeadDimQ,\n                    kPadHeadDim, // kPadHeadDimV,\n                    false, // kHasLogitsSoftCap\n                    ck_tile::BlockAttentionBiasEnum::NO_BIAS,\n                    false, // kHasBiasGrad place-holder\n                    true, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    false, // kIsPagedKV\n                    kHasUnevenSplits,\n                    true, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMaskNone,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              } else {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    true, // kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDim, // kPadHeadDimQ,\n                    kPadHeadDim, // kPadHeadDimV,\n                    false, // kHasLogitsSoftCap\n                    ck_tile::BlockAttentionBiasEnum::NO_BIAS,\n                    false, // kHasBiasGrad place-holder\n                    false, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    false, // kIsPagedKV\n                    kHasUnevenSplits,\n                    true, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMaskNone,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              }\n            });\n      } else {\n        BOOL_SWITCH_3(\n            pad_seqlen_q,\n            kPadSeqLenQ,\n            pad_headdim,\n            kPadHeadDim,\n            has_uneven_splits,\n            kHasUnevenSplits,\n            [&] {\n              constexpr bool kPadSeqLenK = kHasUnevenSplits ? true : false;\n\n              if (param.num_kv_splits > 1) {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDim, // kPadHeadDimQ,\n                    kPadHeadDim, // kPadHeadDimV,\n                    false, // kHasLogitsSoftCap\n                    kBiasEnum,\n                    false, // kHasBiasGrad place-holder\n                    true, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    false, // kIsPagedKV\n                    kHasUnevenSplits,\n                    false, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMask,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              } else {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDim, // kPadHeadDimQ,\n                    kPadHeadDim, // kPadHeadDimV,\n                    false, // kHasLogitsSoftCap\n                    kBiasEnum,\n                    false, // kHasBiasGrad place-holder\n                    false, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    false, // kIsPagedKV\n                    kHasUnevenSplits,\n                    false, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMask,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              }\n            });\n      };\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      const bool pad_seqlen_q = !(param.M % kM0 == 0);\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_v, kPadHeadDimV, [&] {\n            FMHA_FWD_NUM_KV_SPLITS_SWITCH(\n                param.num_kv_splits, kLogMaxSplits, [&] {\n                  using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n                      kPadSeqLenQ,\n                      kPadHeadDimV,\n                      false, // kStoreLSE\n                      false, // kDoFp8StaticQuant place-holder\n                      kLogMaxSplits,\n                      -1>;\n\n                  using FmhaPipelineProblem =\n                      FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n                  using FmhaPipeline =\n                      ck_tile::BlockFmhaFwdSplitKVCombinePipeline<\n                          FmhaPipelineProblem>;\n\n                  using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                      ck_tile::Default2DEpilogueProblem<\n                          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                          kPadSeqLenQ,\n                          kPadHeadDimV>>;\n\n                  using FmhaKernel = ck_tile::\n                      FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n                  RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n                });\n          });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[2],\n            param.q_strides[2], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[2],\n            param.out_acc_strides[3],\n            param.q_strides[0], // q, k, v, bias, lse_acc, out_acc tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[1],\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            nullptr, // lse_ptr\n            param.out_ptr,\n            param.B, // batch\n            param.M, // seqlen_q\n            param.N, // seqlen_k\n            nullptr, // seqlen_k_ptr, not used\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr, not used\n            0, // batch_stride_block_table, not used\n            0, // page_table_size, not used\n            nullptr, // cache_batch_idx, not used\n            param.scale,\n            1.0f, // scale_pz\n            0.f, // logits_soft_cap\n            param.q_strides[1], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[2],\n            param.out_strides[1],\n            param.q_strides[2], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[2],\n            param.v_strides[2],\n            param.attn_bias_strides[1],\n            0, // nhead_stride_lse\n            param.out_strides[2],\n            param.q_strides[0], // q, k, v, bias, lse, out tensor\n                                // batch-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[0],\n            0, // batch_stride_lse\n            param.out_strides[0],\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.B, param.Hq, param.Hkv, param.M, param.Kv, param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      BatchedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          nullptr, // lse_ptr, not used\n          param.out_ptr,\n          param.B, // batches\n          param.M, // seqlen_q\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[2], // row_stride_o_acc\n          param.out_strides[1], // row_stride_o\n          param.lse_acc_strides[2], // head_stride_lse_acc\n          param.out_acc_strides[3], // head_stride_o_acc\n          0, // head_stride_lse, // not used\n          param.out_strides[2], // head_stride_o\n          param.lse_acc_strides[1], // batch_stride_lse_acc\n          param.out_acc_strides[1], // batch_stride_o_acc\n          0, // batch_stride_lse, not used\n          param.out_strides[0], // batch_stride_o\n          param.lse_acc_strides[0], // split_stride_lse_acc\n          param.out_acc_strides[0]); // split_stride_out_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.B, param.Hq, param.M, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_bwd_setting.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/ops/fmha.hpp>\n#include <ck_tile/ops/fmha/block/block_dropout.hpp>\n\ntemplate <typename DataType>\nstruct FmhaBwdTypeConfig;\n\ntemplate <>\nstruct FmhaBwdTypeConfig<ck_tile::fp16_t> {\n  using QDataType = ck_tile::fp16_t;\n  using KDataType = ck_tile::fp16_t;\n  using VDataType = ck_tile::fp16_t;\n  using GemmDataType = ck_tile::fp16_t;\n  using BiasDataType = ck_tile::fp16_t;\n  using RandValOutputDataType = unsigned short;\n  using LSEDataType = float;\n  using AccDataType = float; // data type for gemm accumulation\n  using DDataType = float;\n  using ODataType = ck_tile::fp16_t;\n  using OGradDataType = ck_tile::fp16_t;\n  using QGradDataType = ck_tile::fp16_t;\n  using KGradDataType = ck_tile::fp16_t;\n  using VGradDataType = ck_tile::fp16_t;\n  using BiasGradDataType = ck_tile::fp16_t;\n};\n\ntemplate <>\nstruct FmhaBwdTypeConfig<ck_tile::bf16_t> {\n  using QDataType = ck_tile::bf16_t;\n  using KDataType = ck_tile::bf16_t;\n  using VDataType = ck_tile::bf16_t;\n  using GemmDataType = ck_tile::bf16_t;\n  using BiasDataType = ck_tile::bf16_t;\n  using RandValOutputDataType = unsigned short;\n  using LSEDataType = float;\n  using AccDataType = float; // data type for gemm accumulation\n  using DDataType = float;\n  using ODataType = ck_tile::bf16_t;\n  using OGradDataType = ck_tile::bf16_t;\n  using QGradDataType = ck_tile::bf16_t;\n  using KGradDataType = ck_tile::bf16_t;\n  using VGradDataType = ck_tile::bf16_t;\n  using BiasGradDataType = ck_tile::bf16_t;\n};\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaBwdBlockTile;\n\ntemplate <>\nstruct FmhaBwdBlockTile<32> {\n  using tile_lengths = ck_tile::sequence<32, 128, 32, 32, 32, 32, 64, 32, 32>;\n  using gemm02_warps = ck_tile::sequence<1, 4, 1>; // default for gemm0/gemm2\n  using gemm13_warps = ck_tile::sequence<4, 1, 1>; // default for gemm1/gemm3\n  using gemm4_warps = ck_tile::sequence<2, 2, 1>; // default for gemm4\n};\n\ntemplate <>\nstruct FmhaBwdBlockTile<64> {\n  using tile_lengths = ck_tile::sequence<32, 128, 64, 32, 64, 32, 32, 64, 64>;\n  using gemm02_warps = ck_tile::sequence<1, 4, 1>; // default for gemm0/gemm2\n  using gemm13_warps = ck_tile::sequence<4, 1, 1>; // default for gemm1/gemm3\n  using gemm4_warps = ck_tile::sequence<1, 4, 1>; // default for gemm4\n};\n\ntemplate <>\nstruct FmhaBwdBlockTile<96> {\n  using tile_lengths = ck_tile::sequence<16, 128, 96, 16, 96, 16, 32, 128, 128>;\n  using gemm02_warps = ck_tile::sequence<1, 4, 1>; // default for gemm0/gemm2\n  using gemm13_warps = ck_tile::sequence<4, 1, 1>; // default for gemm1/gemm3\n  using gemm4_warps = ck_tile::sequence<1, 4, 1>; // default for gemm4\n};\n\ntemplate <>\nstruct FmhaBwdBlockTile<128> {\n  using tile_lengths =\n      ck_tile::sequence<16, 128, 128, 16, 128, 16, 32, 128, 128>;\n  using gemm02_warps = ck_tile::sequence<1, 4, 1>; // default for gemm0/gemm2\n  using gemm13_warps = ck_tile::sequence<4, 1, 1>; // default for gemm1/gemm3\n  using gemm4_warps = ck_tile::sequence<1, 4, 1>; // default for gemm4\n};\n\ntemplate <>\nstruct FmhaBwdBlockTile<256> {\n  using tile_lengths =\n      ck_tile::sequence<16, 64, 256, 16, 256, 16, 32, 256, 256>;\n  using gemm02_warps = ck_tile::sequence<1, 4, 1>; // default for gemm0/gemm2\n  using gemm13_warps = ck_tile::sequence<4, 1, 1>; // default for gemm1/gemm3\n  using gemm4_warps = ck_tile::sequence<1, 4, 1>; // default for gemm4\n};\n\nusing FmhaBwdWarpTile1 = ck_tile::sequence<32, 32, 16>;\nusing FmhaBwdWarpTile2 = ck_tile::sequence<16, 16, 32>;\nusing FmhaBwdWarpTile3 = ck_tile::sequence<16, 16, 16>;\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaBwdShape;\n\ntemplate <>\nstruct FmhaBwdShape<32> : ck_tile::TileFmhaBwdShape<\n                              typename FmhaBwdBlockTile<32>::tile_lengths,\n                              typename FmhaBwdBlockTile<32>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<32>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<32>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<32>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<32>::gemm4_warps,\n                              FmhaBwdWarpTile2> {};\n\ntemplate <>\nstruct FmhaBwdShape<64> : ck_tile::TileFmhaBwdShape<\n                              typename FmhaBwdBlockTile<64>::tile_lengths,\n                              typename FmhaBwdBlockTile<64>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<64>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<64>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<64>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<64>::gemm4_warps,\n                              FmhaBwdWarpTile2> {};\n\ntemplate <>\nstruct FmhaBwdShape<96> : ck_tile::TileFmhaBwdShape<\n                              typename FmhaBwdBlockTile<96>::tile_lengths,\n                              typename FmhaBwdBlockTile<96>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<96>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<96>::gemm02_warps,\n                              FmhaBwdWarpTile2,\n                              typename FmhaBwdBlockTile<96>::gemm13_warps,\n                              FmhaBwdWarpTile3,\n                              typename FmhaBwdBlockTile<96>::gemm4_warps,\n                              FmhaBwdWarpTile2> {};\n\ntemplate <>\nstruct FmhaBwdShape<128> : ck_tile::TileFmhaBwdShape<\n                               typename FmhaBwdBlockTile<128>::tile_lengths,\n                               typename FmhaBwdBlockTile<128>::gemm02_warps,\n                               FmhaBwdWarpTile2,\n                               typename FmhaBwdBlockTile<128>::gemm13_warps,\n                               FmhaBwdWarpTile3,\n                               typename FmhaBwdBlockTile<128>::gemm02_warps,\n                               FmhaBwdWarpTile2,\n                               typename FmhaBwdBlockTile<128>::gemm13_warps,\n                               FmhaBwdWarpTile3,\n                               typename FmhaBwdBlockTile<128>::gemm4_warps,\n                               FmhaBwdWarpTile2> {};\n\ntemplate <>\nstruct FmhaBwdShape<256> : ck_tile::TileFmhaBwdShape<\n                               typename FmhaBwdBlockTile<256>::tile_lengths,\n                               typename FmhaBwdBlockTile<256>::gemm02_warps,\n                               FmhaBwdWarpTile2,\n                               typename FmhaBwdBlockTile<256>::gemm13_warps,\n                               FmhaBwdWarpTile3,\n                               typename FmhaBwdBlockTile<256>::gemm02_warps,\n                               FmhaBwdWarpTile2,\n                               typename FmhaBwdBlockTile<256>::gemm13_warps,\n                               FmhaBwdWarpTile3,\n                               typename FmhaBwdBlockTile<256>::gemm4_warps,\n                               FmhaBwdWarpTile2> {};\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaBwdPipelineEnumSelector {\n  static constexpr ck_tile::BlockFmhaBwdPipelineEnum value =\n      ck_tile::BlockFmhaBwdPipelineEnum::KRKTRVR_IGLP;\n};\n\ntemplate <ck_tile::BlockFmhaBwdPipelineEnum value, typename problem>\nstruct FmhaBwdPipelineMaker;\n\ntemplate <typename problem>\nstruct FmhaBwdPipelineMaker<\n    ck_tile::BlockFmhaBwdPipelineEnum::KRKTRVR,\n    problem> {\n  using pipeline = ck_tile::BlockFmhaBwdDQDKDVPipelineKRKTRVR<problem>;\n};\n\ntemplate <typename problem>\nstruct FmhaBwdPipelineMaker<\n    ck_tile::BlockFmhaBwdPipelineEnum::KRKTRVR_IGLP,\n    problem> {\n  using pipeline = ck_tile::BlockFmhaBwdDQDKDVPipelineKRKTRVRIGLP<problem>;\n};\n\ntemplate <bool kHasDropout, ck_tile::index_t MaxK>\nstruct FmhaBwdBlockDropoutMaker;\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaBwdBlockDropoutMaker<false, MaxK> {\n  using dropout = ck_tile::BlockDropoutBwd<false, true, false>;\n};\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaBwdBlockDropoutMaker<true, MaxK> {\n  using FmhaBwdShapeType = FmhaBwdShape<MaxK>;\n  static constexpr bool IsWG32 =\n      (FmhaBwdShapeType::Gemm0WarpTile::at(ck_tile::number<0>{}) == 32);\n  using dropout = ck_tile::BlockDropoutBwd<true, IsWG32, false>;\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_setting.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/ops/fmha.hpp>\n#include \"ck_fmha_util.h\"\n#include \"ck_tiled_fmha_fwd_type_config.h\"\n\ntemplate <ck_tile::index_t MaxK, ck_tile::index_t MTile = 0>\nstruct FmhaFwdBlockTile;\n\n// Tile-sizes: M N0 K0 N1 K1 MaxK (MaxK % K0 == 0, MaxK % N1 == 0, N0 % K1 == 0)\n//\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdBlockTile<32, MTile> {\n  using type = ck_tile::sequence<64, 64, 16, 32, 32, 32>;\n  using gemm0_warps = ck_tile::sequence<2, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<2, 1, 1>;\n};\n\ntemplate struct FmhaFwdBlockTile<32>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdBlockTile<64, MTile> {\n  using type = ck_tile::sequence<128, 64, 32, 64, 32, 64>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdBlockTile<64>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdBlockTile<96, MTile> {\n  using type = ck_tile::sequence<128, 128, 32, 128, 32, 96>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdBlockTile<96>;\n\ntemplate <>\nstruct FmhaFwdBlockTile<128, 64> {\n  using type = ck_tile::sequence<64, 128, 32, 128, 32, 128>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdBlockTile<128, 128> {\n  using type = ck_tile::sequence<128, 128, 32, 128, 32, 128>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdBlockTile<256, MTile> {\n  using type = ck_tile::sequence<128, 128, 32, 256, 32, 256>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdBlockTile<256>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdBlockTile<512, MTile> {\n  using type = ck_tile::sequence<64, 128, 32, 512, 32, 512>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdBlockTile<512>;\n\nusing FmhaFwdWarpTile1 = ck_tile::sequence<32, 32, 16>;\nusing FmhaFwdWarpTile2 = ck_tile::sequence<16, 16, 16>;\nusing FmhaFwdWarpTile3 = ck_tile::sequence<16, 16, 32>;\n\ntemplate <ck_tile::index_t MaxK, ck_tile::index_t MTile>\nstruct FmhaFwdShape;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdShape<32, MTile> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<32>::type,\n      typename FmhaFwdBlockTile<32>::gemm0_warps,\n      FmhaFwdWarpTile1,\n      typename FmhaFwdBlockTile<32>::gemm1_warps,\n      FmhaFwdWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdShape<32, 64>;\ntemplate struct FmhaFwdShape<32, 128>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdShape<64, MTile> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<64>::type,\n      typename FmhaFwdBlockTile<64>::gemm0_warps,\n      FmhaFwdWarpTile1,\n      typename FmhaFwdBlockTile<64>::gemm1_warps,\n      FmhaFwdWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdShape<64, 64>;\ntemplate struct FmhaFwdShape<64, 128>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdShape<96, MTile> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<96>::type,\n      typename FmhaFwdBlockTile<96>::gemm0_warps,\n      FmhaFwdWarpTile1,\n      typename FmhaFwdBlockTile<96>::gemm1_warps,\n      FmhaFwdWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdShape<96, 64>;\ntemplate struct FmhaFwdShape<96, 128>;\n\ntemplate <>\nstruct FmhaFwdShape<128, 64> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<128, 64>::type,\n      typename FmhaFwdBlockTile<128, 64>::gemm0_warps,\n      FmhaFwdWarpTile3,\n      typename FmhaFwdBlockTile<128, 64>::gemm1_warps,\n      FmhaFwdWarpTile2,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdShape<128, 128> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<128, 128>::type,\n      typename FmhaFwdBlockTile<128, 128>::gemm0_warps,\n      FmhaFwdWarpTile1,\n      typename FmhaFwdBlockTile<128, 128>::gemm1_warps,\n      FmhaFwdWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdShape<256, MTile> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<256>::type,\n      typename FmhaFwdBlockTile<256>::gemm0_warps,\n      FmhaFwdWarpTile1,\n      typename FmhaFwdBlockTile<256>::gemm1_warps,\n      FmhaFwdWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdShape<256, 64>;\ntemplate struct FmhaFwdShape<256, 128>;\n\ntemplate <ck_tile::index_t MTile>\nstruct FmhaFwdShape<512, MTile> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdBlockTile<512>::type,\n      typename FmhaFwdBlockTile<512>::gemm0_warps,\n      FmhaFwdWarpTile2,\n      typename FmhaFwdBlockTile<512>::gemm1_warps,\n      FmhaFwdWarpTile2,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdShape<512, 64>;\ntemplate struct FmhaFwdShape<512, 128>;\n\nstatic int get_fmha_fwd_mtile(\n    int num_batches,\n    int num_heads,\n    int max_seqlen_q) {\n  int num_SMs = get_number_of_cu();\n  auto ceildiv = [](int a, int b) { return (a + b - 1) / b; };\n\n  int batch_nhead_mblocks =\n      num_batches * num_heads * ceildiv(max_seqlen_q, 128);\n\n  if (batch_nhead_mblocks >= 0.8 * num_SMs)\n    return 128;\n\n  // currently, only hdim-128 can use mtile-64, for other hdim, the settings for\n  // mtile-64 can be added through tuning/verification\n  return 64;\n};\n\nstatic int get_fmha_fwd_least_mtile() {\n  return 64;\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_splitkv_selector.h",
    "content": "/*\n * Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <cmath>\n#include <tuple>\n#include \"ck_fmha_util.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\n// generate a list of numbers as num_splits to consider, the list of numbers is\n// like 1, 2, 4, 8, 16, 32, 64, 96, 128, 160\nstatic int generate_splits_list(int i) {\n  if (i <= 0)\n    return 1;\n\n  if (i <= 5)\n    return 1 << (i - 1);\n  else\n    return (i - 5) * 32;\n};\n\nstatic std::pair<bool, int> get_num_kv_splits_heuristic(\n    int num_batches,\n    int num_heads,\n    int max_seqlen_q,\n    int max_headdim,\n    int max_splits) {\n  int num_SMs = get_number_of_cu();\n  auto ceildiv = [](int a, int b) { return (a + b - 1) / b; };\n\n  int mtile_size_for_pipeline_default = get_fmha_fwd_least_mtile();\n  int mtile_size_for_splitkv = 64;\n  int mtile_size_for_splitkv_smallq = 16;\n\n  // get mtile_size_for_splitkv\n  mtile_size_for_splitkv =\n      get_mtile_size_for_splitkv(max_seqlen_q, max_headdim);\n\n  // get mtile_size_for_splitkv_smallq\n  mtile_size_for_splitkv_smallq =\n      get_mtile_size_for_splitkv_smallq(max_headdim);\n\n  // hdim-512 is not supported by splitkv-kernel at present\n  if (max_headdim > 256)\n    return std::make_pair(false, 1);\n\n  if (max_seqlen_q >= mtile_size_for_pipeline_default) {\n    int batch_nhead_mblocks = num_batches * num_heads *\n        ceildiv(max_seqlen_q, mtile_size_for_pipeline_default);\n\n    if (batch_nhead_mblocks >= 0.8f * num_SMs)\n      return std::make_pair(false, 1);\n  }\n\n  bool use_splitkv = true;\n\n  // m_tile size is the size for dividing the seqlen_q\n  // we first tries to use the normal splitkv kernel\n  int mtile_size = mtile_size_for_splitkv;\n  int batch_nhead_mblocks =\n      num_batches * num_heads * ceildiv(max_seqlen_q, mtile_size);\n\n  // resort to splitkv-smallq kernel for avoiding wasting of computation or for\n  // better CU occupancy\n  if (max_seqlen_q <= mtile_size_for_splitkv_smallq)\n    mtile_size = mtile_size_for_splitkv_smallq;\n\n  batch_nhead_mblocks =\n      num_batches * num_heads * ceildiv(max_seqlen_q, mtile_size);\n\n  // If we have enough workgroups to fill all the SMs, then just use 1 split\n  if (batch_nhead_mblocks >= 0.9f * num_SMs) {\n    return std::make_pair(use_splitkv, 1);\n  }\n\n  max_splits = std::min({max_splits, num_SMs});\n\n  int max_check = 1;\n\n  while (generate_splits_list(max_check) <= max_splits)\n    max_check++;\n\n  int num_splits = 2;\n  for (int i = 2; i < max_check; i++) {\n    num_splits = generate_splits_list(i);\n\n    if (batch_nhead_mblocks * num_splits >= num_SMs)\n      break;\n  };\n\n  return std::make_pair(use_splitkv, num_splits);\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_splitkv_setting.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/ops/fmha.hpp>\n#include \"ck_tiled_fmha_fwd_type_config.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\ntemplate <ck_tile::index_t MaxK, ck_tile::index_t MaxSeqLenQ = 0>\nstruct FmhaFwdSplitKVBlockTile;\n\n// Tile-sizes: M N0 K0 N1 K1 MaxK (MaxK % K0 == 0, MaxK % N1 == 0, N0 % K1 == 0)\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVBlockTile<32, MaxSeqLenQ> {\n  using type = ck_tile::sequence<32, 64, 16, 32, 32, 32>;\n  using gemm0_warps = ck_tile::sequence<2, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<2, 1, 1>;\n};\n\ntemplate struct FmhaFwdSplitKVBlockTile<32>;\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVBlockTile<64, MaxSeqLenQ> {\n  using type = ck_tile::sequence<32, 64, 32, 64, 32, 64>;\n  using gemm0_warps = ck_tile::sequence<2, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<2, 1, 1>;\n};\n\ntemplate struct FmhaFwdSplitKVBlockTile<64>;\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVBlockTile<96, MaxSeqLenQ> {\n  using type = ck_tile::sequence<64, 128, 32, 128, 32, 96>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdSplitKVBlockTile<96>;\n\ntemplate <>\nstruct FmhaFwdSplitKVBlockTile<128, 32> {\n  using type = ck_tile::sequence<32, 128, 32, 128, 32, 128>;\n  using gemm0_warps = ck_tile::sequence<2, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<2, 1, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVBlockTile<128, 64> {\n  using type = ck_tile::sequence<64, 128, 32, 128, 32, 128>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVBlockTile<256, MaxSeqLenQ> {\n  using type = ck_tile::sequence<64, 128, 32, 256, 32, 256>;\n  using gemm0_warps = ck_tile::sequence<4, 1, 1>;\n  using gemm1_warps = ck_tile::sequence<4, 1, 1>;\n};\n\ntemplate struct FmhaFwdSplitKVBlockTile<256>;\n\nusing FmhaFwdSplitKVWarpTile = ck_tile::sequence<16, 16, 16>;\n\ntemplate <ck_tile::index_t MaxK, ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVShape;\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVShape<32, MaxSeqLenQ> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<32>::type,\n      typename FmhaFwdSplitKVBlockTile<32>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<32>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdSplitKVShape<32, 32>;\ntemplate struct FmhaFwdSplitKVShape<32, 64>;\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVShape<64, MaxSeqLenQ> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<64>::type,\n      typename FmhaFwdSplitKVBlockTile<64>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<64, MaxSeqLenQ>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdSplitKVShape<64, 32>;\ntemplate struct FmhaFwdSplitKVShape<64, 64>;\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVShape<96, MaxSeqLenQ> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<96>::type,\n      typename FmhaFwdSplitKVBlockTile<96>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<96, MaxSeqLenQ>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdSplitKVShape<96, 32>;\ntemplate struct FmhaFwdSplitKVShape<96, 64>;\n\ntemplate <>\nstruct FmhaFwdSplitKVShape<128, 32> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<128, 32>::type,\n      typename FmhaFwdSplitKVBlockTile<128, 32>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<128, 32>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVShape<128, 64> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<128, 64>::type,\n      typename FmhaFwdSplitKVBlockTile<128, 64>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<128, 64>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <ck_tile::index_t MaxSeqLenQ>\nstruct FmhaFwdSplitKVShape<256, MaxSeqLenQ> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVBlockTile<256>::type,\n      typename FmhaFwdSplitKVBlockTile<256>::gemm0_warps,\n      FmhaFwdSplitKVWarpTile,\n      typename FmhaFwdSplitKVBlockTile<256>::gemm1_warps,\n      FmhaFwdSplitKVWarpTile,\n      IsVLayoutRowMajor>;\n};\n\ntemplate struct FmhaFwdSplitKVShape<256, 32>;\ntemplate struct FmhaFwdSplitKVShape<256, 64>;\n\ntemplate <ck_tile::index_t MaxK, ck_tile::index_t MaxSeqLenQ>\nint fwd_splitkv_get_mtile_size() {\n  using FmhaTileShape = typename FmhaFwdSplitKVShape<MaxK, MaxSeqLenQ>::Type;\n\n  return FmhaTileShape::kM0;\n};\n\nstatic int get_mtile_size_for_splitkv(int max_seqlen_q, int max_headdim) {\n  int mtile_size_for_splitkv = 64;\n\n  FMHA_FWD_SEQLEN_Q_SWITCH(max_seqlen_q, MaxSeqLenQ, [&] {\n    if (max_headdim <= 32) {\n      mtile_size_for_splitkv = fwd_splitkv_get_mtile_size<32, MaxSeqLenQ>();\n    } else if (max_headdim <= 64) {\n      mtile_size_for_splitkv = fwd_splitkv_get_mtile_size<64, MaxSeqLenQ>();\n    } else if (max_headdim <= 96) {\n      mtile_size_for_splitkv = fwd_splitkv_get_mtile_size<96, MaxSeqLenQ>();\n    } else if (max_headdim <= 128) {\n      mtile_size_for_splitkv = fwd_splitkv_get_mtile_size<128, MaxSeqLenQ>();\n    } else {\n      mtile_size_for_splitkv = fwd_splitkv_get_mtile_size<256, MaxSeqLenQ>();\n    };\n  });\n\n  return mtile_size_for_splitkv;\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_splitkv_smallq_selector.h",
    "content": "/*\n * Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n\n/// This method determines whether to use normal or smallq splitkv kernel\nstatic bool use_splitkv_smallq(int max_seqlen_q, int max_headdim) {\n  int mtile_size_for_splitkv_smallq =\n      get_mtile_size_for_splitkv_smallq(max_headdim);\n\n  // resort to splitkv-smallq kernel for avoiding wasting of computation\n  if (max_seqlen_q <= mtile_size_for_splitkv_smallq)\n    return true;\n\n  return false;\n}\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_splitkv_smallq_setting.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/ops/fmha.hpp>\n#include \"ck_tiled_fmha_fwd_type_config.h\"\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaFwdSplitKVSmallQBlockTile;\n\n// Tile-sizes: M N0 K0 N1 K1 MaxK (MaxK % K0 == 0, MaxK % N1 == 0, N0 % K1 == 0)\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQBlockTile<32> {\n  using type = ck_tile::sequence<16, 64, 16, 32, 32, 32>;\n  using gemm0_warps = ck_tile::sequence<1, 2, 1>;\n  using gemm1_warps = ck_tile::sequence<1, 2, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQBlockTile<64> {\n  using type = ck_tile::sequence<16, 64, 32, 64, 32, 64>;\n  using gemm0_warps = ck_tile::sequence<1, 4, 1>;\n  using gemm1_warps = ck_tile::sequence<1, 4, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQBlockTile<96> {\n  using type = ck_tile::sequence<16, 64, 32, 128, 32, 96>;\n  using gemm0_warps = ck_tile::sequence<1, 4, 1>;\n  using gemm1_warps = ck_tile::sequence<1, 4, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQBlockTile<128> {\n  using type = ck_tile::sequence<16, 64, 64, 128, 64, 128>;\n  using gemm0_warps = ck_tile::sequence<1, 4, 1>;\n  using gemm1_warps = ck_tile::sequence<1, 4, 1>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQBlockTile<256> {\n  using type = ck_tile::sequence<16, 64, 64, 256, 64, 256>;\n  using gemm0_warps = ck_tile::sequence<1, 4, 1>;\n  using gemm1_warps = ck_tile::sequence<1, 4, 1>;\n};\n\nusing FmhaFwdSplitKVSmallQWarpTile0 = ck_tile::sequence<16, 16, 16>;\nusing FmhaFwdSplitKVSmallQWarpTile1 = ck_tile::sequence<16, 16, 16>;\n\ntemplate <ck_tile::index_t MaxK>\nstruct FmhaFwdSplitKVSmallQShape;\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQShape<32> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVSmallQBlockTile<32>::type,\n      typename FmhaFwdSplitKVSmallQBlockTile<32>::gemm0_warps,\n      FmhaFwdSplitKVSmallQWarpTile0,\n      typename FmhaFwdSplitKVSmallQBlockTile<32>::gemm1_warps,\n      FmhaFwdSplitKVSmallQWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQShape<64> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVSmallQBlockTile<64>::type,\n      typename FmhaFwdSplitKVSmallQBlockTile<64>::gemm0_warps,\n      FmhaFwdSplitKVSmallQWarpTile0,\n      typename FmhaFwdSplitKVSmallQBlockTile<64>::gemm1_warps,\n      FmhaFwdSplitKVSmallQWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQShape<96> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVSmallQBlockTile<96>::type,\n      typename FmhaFwdSplitKVSmallQBlockTile<96>::gemm0_warps,\n      FmhaFwdSplitKVSmallQWarpTile0,\n      typename FmhaFwdSplitKVSmallQBlockTile<96>::gemm1_warps,\n      FmhaFwdSplitKVSmallQWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQShape<128> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVSmallQBlockTile<128>::type,\n      typename FmhaFwdSplitKVSmallQBlockTile<128>::gemm0_warps,\n      FmhaFwdSplitKVSmallQWarpTile0,\n      typename FmhaFwdSplitKVSmallQBlockTile<128>::gemm1_warps,\n      FmhaFwdSplitKVSmallQWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <>\nstruct FmhaFwdSplitKVSmallQShape<256> {\n  using Type = ck_tile::TileFmhaShape<\n      typename FmhaFwdSplitKVSmallQBlockTile<256>::type,\n      typename FmhaFwdSplitKVSmallQBlockTile<256>::gemm0_warps,\n      FmhaFwdSplitKVSmallQWarpTile0,\n      typename FmhaFwdSplitKVSmallQBlockTile<256>::gemm1_warps,\n      FmhaFwdSplitKVSmallQWarpTile1,\n      IsVLayoutRowMajor>;\n};\n\ntemplate <ck_tile::index_t MaxK>\nint fwd_splitkv_smallq_get_mtile_size() {\n  using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n  return FmhaTileShape::kM0;\n};\n\nstatic int get_mtile_size_for_splitkv_smallq(int max_headdim) {\n  int mtile_size_for_splitkv_smallq = 16;\n\n  if (max_headdim <= 32) {\n    mtile_size_for_splitkv_smallq = fwd_splitkv_smallq_get_mtile_size<32>();\n  } else if (max_headdim <= 64) {\n    mtile_size_for_splitkv_smallq = fwd_splitkv_smallq_get_mtile_size<64>();\n  } else if (max_headdim <= 96) {\n    mtile_size_for_splitkv_smallq = fwd_splitkv_smallq_get_mtile_size<96>();\n  } else if (max_headdim <= 128) {\n    mtile_size_for_splitkv_smallq = fwd_splitkv_smallq_get_mtile_size<128>();\n  } else {\n    mtile_size_for_splitkv_smallq = fwd_splitkv_smallq_get_mtile_size<256>();\n  };\n\n  return mtile_size_for_splitkv_smallq;\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_fwd_type_config.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n\ntemplate <typename DataType>\nstruct FmhaFwdTypeConfig;\n\ntemplate <>\nstruct FmhaFwdTypeConfig<ck_tile::fp16_t> {\n  using QDataType = ck_tile::fp16_t;\n  using KDataType = ck_tile::fp16_t;\n  using VDataType = ck_tile::fp16_t;\n  using BiasDataType = ck_tile::fp16_t;\n  using RandValOutputDataType = unsigned short;\n  using LSEDataType =\n      float; // data type for lse(logsumexp L_j = max_j + log(l_j))\n  using SaccDataType = float; // data type for first gemm accumulation\n  using SMPLComputeDataType = float; // data type for reduction, softmax\n  using PDataType = ck_tile::fp16_t; // data type for A matrix of second gemm\n  using OaccDataType = float; // data type for second gemm accumulation\n  using ODataType = ck_tile::fp16_t;\n};\n\ntemplate <>\nstruct FmhaFwdTypeConfig<ck_tile::bf16_t> {\n  using QDataType = ck_tile::bf16_t;\n  using KDataType = ck_tile::bf16_t;\n  using VDataType = ck_tile::bf16_t;\n  using BiasDataType = ck_tile::bf16_t;\n  using RandValOutputDataType = unsigned short;\n  using LSEDataType =\n      float; // data type for lse(logsumexp L_j = max_j + log(l_j))\n  using SaccDataType = float; // data type for first gemm accumulation\n  using SMPLComputeDataType = float; // data type for reduction, softmax\n  using PDataType = ck_tile::bf16_t; // data type for A matrix of second gemm\n  using OaccDataType = float; // data type for second gemm accumulation\n  using ODataType = ck_tile::bf16_t;\n};\n\nstatic constexpr bool IsVLayoutRowMajor = true;\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_backward.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_bwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasBiasGrad,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nstruct grouped_backward_mask_bias_dropout_dispatch {\n  using FmhaBlockDropout =\n      typename FmhaBwdBlockDropoutMaker<kHasDropout, MaxK>::dropout;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaBwdPipelineProblemTemp = ck_tile::BlockFmhaBwdPipelineProblem<\n      typename FmhaBwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::GemmDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::DDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::ODataType,\n      typename FmhaBwdTypeConfig<ScalarType>::OGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::QGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::KGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::VGradDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::BiasGradDataType,\n      FmhaBwdShape<MaxK>,\n      true, // kIsGroupMode\n      false, // non-deterministic\n      FmhaMask,\n      FmhaBlockDropout,\n      FmhaTraits>;\n\n  static constexpr bool NeedConvertGradQ = !std::is_same<\n      typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n      typename FmhaBwdTypeConfig<ScalarType>::QGradDataType>::value;\n\n  static void Run(GroupedBackwardParams& param, hipStream_t stream) {\n    {\n      constexpr ck_tile::index_t kBlockSize = 64;\n      bool pad_headdim_v = !(param.Kv % MaxK == 0);\n\n      constexpr bool kPadSeqLenQ = true;\n\n      BOOL_SWITCH(pad_headdim_v, kPadHeadDimV, [&] {\n        constexpr ck_tile::index_t occupancy = 2;\n\n        using FmhaOGradDotOTraits_ = ck_tile::\n            TileFmhaBwdOGradDotOTraits<kPadSeqLenQ, kPadHeadDimV, occupancy>;\n\n        using FmhaBwdOGradDotOPipelineProblem =\n            ck_tile::BlockFmhaBwdOGradDotOPipelineProblem<\n                typename FmhaBwdTypeConfig<ScalarType>::ODataType,\n                typename FmhaBwdTypeConfig<ScalarType>::OGradDataType,\n                typename FmhaBwdTypeConfig<ScalarType>::DDataType,\n                kBlockSize,\n                MaxK, // kVHeaddim\n                true, // kIsGroupMode\n                FmhaOGradDotOTraits_>;\n\n        using FmhaBwdOGradDotOPipeline_ =\n            typename ck_tile::BlockFmhaBwdOGradDotO<\n                FmhaBwdOGradDotOPipelineProblem>;\n\n        using FmhaBwdOGradDotOKernel_ =\n            ck_tile::FmhaBwdOGradDotOKernel<FmhaBwdOGradDotOPipeline_>;\n\n        RunWithBwdOGradDotOKernel<FmhaBwdOGradDotOKernel_>(param, stream);\n      });\n    };\n\n    {\n      constexpr ck_tile::index_t occupancy = 1;\n      const bool has_dropout = (param.dropout_prob > 0.0f);\n\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      const bool pad_headdim_q =\n          !(param.K % FmhaBwdShape<MaxK>::kQKHeaddim == 0);\n      const bool pad_headdim_v =\n          !(param.Kv % FmhaBwdShape<MaxK>::kVHeaddim == 0);\n\n      BOOL_SWITCH_2(\n          pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n            using FmhaBwdTraits_ = ck_tile::TileFmhaTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ,\n                kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                kHasBiasGrad,\n                false, // kStoreLSE\n                false, // place-holder for kHasDropout, not used actually\n                false, // kDoFp8StaticQuant place-holder\n                occupancy>;\n\n            using FmhaBwdPipelineProblem =\n                FmhaBwdPipelineProblemTemp<FmhaBwdTraits_, FmhaMask>;\n\n            constexpr auto FmhaBwdPipelineEnum_ =\n                FmhaBwdPipelineEnumSelector<MaxK>::value;\n\n            using FmhaBwdPipeline_ = typename FmhaBwdPipelineMaker<\n                FmhaBwdPipelineEnum_,\n                FmhaBwdPipelineProblem>::pipeline;\n\n            using FmhaBwdKGradEpilogue_ =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::KGradDataType,\n                    kPadSeqLenK,\n                    kPadHeadDimQ>>;\n\n            using FmhaBwdVGradEpilogue_ =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::VGradDataType,\n                    kPadSeqLenK,\n                    kPadHeadDimV>>;\n\n            using FmhaBwdDQDKDVKernel_ = ck_tile::FmhaBwdDQDKDVKernel<\n                FmhaBwdPipeline_,\n                FmhaBwdKGradEpilogue_,\n                FmhaBwdVGradEpilogue_>;\n\n            RunWithBwdDQDKDVKernel<FmhaBwdDQDKDVKernel_>(param, stream);\n          });\n    };\n\n    if constexpr (NeedConvertGradQ) {\n      constexpr ck_tile::index_t kBlockSize = 128;\n\n      const bool pad_seqlen_q = true;\n      const bool pad_headdim_q = !(param.K % MaxK == 0);\n\n      BOOL_SWITCH_2(\n          pad_seqlen_q, kPadSeqLenQ, pad_headdim_q, kPadHeadDimQ, [&] {\n            constexpr ck_tile::index_t occupancy = 2;\n\n            using FmhaBwdConvertQGradTraits_ =\n                ck_tile::TileFmhaBwdConvertQGradTraits<\n                    kPadSeqLenQ,\n                    kPadHeadDimQ,\n                    occupancy>;\n\n            using FmhaBwdConvertQGradPipelineProblem =\n                ck_tile::BlockFmhaBwdConvertQGradPipelineProblem<\n                    typename FmhaBwdTypeConfig<ScalarType>::AccDataType,\n                    typename FmhaBwdTypeConfig<ScalarType>::QGradDataType,\n                    kBlockSize,\n                    64, // kM0\n                    1, // kN0, no use\n                    MaxK, // kQKHeaddim\n                    true, // kIsGroupMode\n                    false, // kIsDeterministic\n                    FmhaBwdConvertQGradTraits_>;\n\n            using FmhaBwdConvertQGradPipeline =\n                typename ck_tile::BlockFmhaBwdConvertQGrad<\n                    FmhaBwdConvertQGradPipelineProblem>;\n\n            using FmhaBwdConvertQGradKernel_ =\n                ck_tile::FmhaBwdConvertQGradKernel<FmhaBwdConvertQGradPipeline>;\n\n            RunWithBwdConvertQGradKernel<FmhaBwdConvertQGradKernel_>(\n                param, stream);\n          });\n    };\n  }\n\n  template <typename FmhaBwdOGradDotOKernel>\n  static void RunWithBwdOGradDotOKernel(\n      GroupedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdOGradDotOKernel::MakeKargs(\n          param.out_ptr,\n          param.grad_out_ptr,\n          param.dot_out_ptr,\n          1.0f - param.dropout_prob,\n          param.seqstart_q_dev_ptr,\n          param.Kv,\n          param.grad_out_strides[0], // stride_do\n          param.out_strides[0], // stride_o\n          param.grad_out_strides[1], // nhead_stride_do\n          param.out_strides[1], // nhead_stride_o\n          param.lsed_strides[0]); // nhead_stride_d\n    }();\n\n    dim3 kGridSize = FmhaBwdOGradDotOKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q);\n    constexpr dim3 kBlockSize = FmhaBwdOGradDotOKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaBwdOGradDotOKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdOGradDotOKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n\n  template <typename FmhaBwdDQDKDVKernel>\n  static void RunWithBwdDQDKDVKernel(\n      GroupedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdDQDKDVKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          param.logsumexp_ptr,\n          param.grad_out_ptr,\n          param.dot_out_ptr,\n          nullptr, // randval_ptr\n          param.grad_k_ptr,\n          param.grad_v_ptr,\n          param.grad_bias_ptr,\n          NeedConvertGradQ ? param.grad_q_f32_ptr : param.grad_q_ptr,\n          param.seqstart_q_dev_ptr,\n          param.seqstart_k_dev_ptr,\n          param.seqlen_k_dev_ptr,\n          param.K,\n          param.Kv,\n          param.Hq,\n          param.Hq / param.Hkv,\n          param.scale,\n          param.q_strides[0], // q, k, v, bias, do, dq_f32, dk, dv, dbias\n                              // seq-dim stride\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[1],\n          0, // stride_randval\n          param.grad_out_strides[0],\n          NeedConvertGradQ ? param.grad_q_f32_strides[0] : param.q_strides[0],\n          param.grad_k_strides[0],\n          param.grad_v_strides[0],\n          param.attn_bias_strides[1], // assume grad_bias has same strides as\n                                      // bias.\n          param.q_strides[1], // q, k, v, bias, do, lse/dot, dq_f32, dk, dv,\n                              // dbias nhead-dim strides\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[0],\n          0, // nhead_stride_randval\n          param.grad_out_strides[1],\n          param.lsed_strides[0], // assume lse/dot is in HM contiguous layout\n          NeedConvertGradQ ? param.grad_q_f32_strides[1] : param.q_strides[1],\n          param.grad_k_strides[1],\n          param.grad_v_strides[1],\n          param.attn_bias_strides[0], // assume grad_bias has same strides as\n                                      // bias\n          0, // split_stride_dq_acc\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          param.dropout_prob, // dropout ratio\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize = FmhaBwdDQDKDVKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_k);\n    constexpr dim3 kBlockSize = FmhaBwdDQDKDVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaBwdDQDKDVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdDQDKDVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n\n  template <typename FmhaBwdConvertQGradKernel>\n  static void RunWithBwdConvertQGradKernel(\n      GroupedBackwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaBwdConvertQGradKernel::MakeKargs(\n          param.grad_q_f32_ptr,\n          param.grad_q_ptr,\n          param.seqstart_q_dev_ptr,\n          param.seqstart_k_dev_ptr,\n          param.K, // headdim of q/k\n          param.q_strides[0],\n          param.grad_q_f32_strides[0],\n          param.q_strides[1],\n          param.grad_q_f32_strides[1],\n          0);\n    }();\n\n    dim3 kGridSize = FmhaBwdConvertQGradKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q);\n    constexpr dim3 kBlockSize = FmhaBwdConvertQGradKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaBwdConvertQGradKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaBwdConvertQGradKernel{}, kGridSize, kBlockSize, 0, kargs));\n  }\n};\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasBiasGrad,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_grouped_backward_mask_bias_dropout_dispatch(\n    GroupedBackwardParams& param,\n    hipStream_t stream) {\n  grouped_backward_mask_bias_dropout_dispatch<\n      ScalarType,\n      kHasMask,\n      kHasBias,\n      kHasBiasGrad,\n      kHasDropout,\n      MaxK>::Run(param, stream);\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_backward_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_backward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_grouped_backward_bf16_instances_ref.h\"\n\nvoid grouped_backward_bf16(GroupedBackwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_3(\n      param.has_attn_bias,\n      kHasBias,\n      param.bias_has_grad,\n      kHasBiasGrad,\n      has_dropout,\n      kHasDropout,\n      [&] {\n        if constexpr (kHasBias || !kHasBiasGrad) {\n          FMHA_BWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n            if (param.custom_mask_type == 0 && param.window_size <= 0)\n              run_grouped_backward_mask_bias_dropout_dispatch<\n                  ck_tile::bf16_t,\n                  false,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else if (\n                param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n                param.window_size > 0)\n              run_grouped_backward_mask_bias_dropout_dispatch<\n                  ck_tile::bf16_t,\n                  true,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else\n              throw std::runtime_error(\"Invalid custom_mask_type value\");\n          });\n        } else\n          throw std::runtime_error(\n              \"bias_has_grad should be false when has_attn_bias is false!\");\n      });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_backward_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_backward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_grouped_backward_fp16_instances_ref.h\"\n\nvoid grouped_backward_fp16(GroupedBackwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_3(\n      param.has_attn_bias,\n      kHasBias,\n      param.bias_has_grad,\n      kHasBiasGrad,\n      has_dropout,\n      kHasDropout,\n      [&] {\n        if constexpr (kHasBias || !kHasBiasGrad) {\n          FMHA_BWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n            if (param.custom_mask_type == 0 && param.window_size <= 0)\n              run_grouped_backward_mask_bias_dropout_dispatch<\n                  ck_tile::fp16_t,\n                  false,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else if (\n                param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n                param.window_size > 0)\n              run_grouped_backward_mask_bias_dropout_dispatch<\n                  ck_tile::fp16_t,\n                  true,\n                  kHasBias,\n                  kHasBiasGrad,\n                  kHasDropout,\n                  MaxK>(param, stream);\n            else\n              throw std::runtime_error(\"Invalid custom_mask_type value\");\n          });\n        } else\n          throw std::runtime_error(\n              \"bias_has_grad should be false when has_attn_bias is false!\");\n      });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <algorithm>\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_selector.h\"\n#include \"ck_tiled_fmha_grouped_forward_dispatch.h\"\n#include \"ck_tiled_fmha_grouped_forward_splitkv_dispatch.h\"\n#include \"ck_tiled_fmha_grouped_forward_splitkv_smallq_dispatch.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_grouped_forward_mask_bias_dropout_dispatch(\n    GroupedForwardParams& param,\n    hipStream_t stream) {\n  // currently split-kv implementation does not support:\n  // (*) dropout\n  // (*) head dimension > 256\n  if constexpr (!kHasDropout) {\n    if (param.use_split_kv && MaxK <= 256) {\n      if constexpr (MaxK <= 256) {\n        if (use_splitkv_smallq(\n                param.max_seqlen_q, std::max(param.K, param.Kv))) {\n          grouped_forward_splitkv_smallq_mask_bias_dropout_dispatch<\n              ScalarType,\n              kHasMask,\n              kHasBias,\n              MaxK>::Run(param, stream);\n        } else {\n          FMHA_FWD_SEQLEN_Q_SWITCH(param.max_seqlen_q, MaxSeqlenQ, [&] {\n            grouped_forward_splitkv_mask_bias_dropout_dispatch<\n                ScalarType,\n                kHasMask,\n                kHasBias,\n                MaxK,\n                MaxSeqlenQ>::Run(param, stream);\n          });\n        }\n      } else {\n        // Unreachable. Do not instantiate split-kv pipelines with head\n        // dimension > 256\n      }\n    } else {\n      if (get_fmha_fwd_mtile(param.num_batches, param.Hq, param.max_seqlen_q) ==\n          128)\n        grouped_forward_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            128>::Run(param, stream);\n      else\n        grouped_forward_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            64>::Run(param, stream);\n    }\n  } else {\n    // at present, dropout of fwd kernel requires 32x32 WarpTile\n    grouped_forward_mask_bias_dropout_dispatch<\n        ScalarType,\n        kHasMask,\n        kHasBias,\n        kHasDropout,\n        MaxK,\n        128>::Run(param, stream);\n  }\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_forward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_grouped_forward_bf16_instances_ref.h\"\n\nvoid grouped_forward_bf16(GroupedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_grouped_forward_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_grouped_forward_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MTile>\nstruct grouped_forward_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaPipelineProblemTemp = ck_tile::BlockFmhaPipelineProblem<\n      typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n      typename FmhaFwdShape<MaxK, MTile>::Type,\n      true, // kIsGroupMode\n      AttentionVariant<FmhaTraits>,\n      FmhaMask,\n      FmhaTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n    using FmhaFwdShape_ = typename FmhaFwdShape<MaxK, MTile>::Type;\n\n    constexpr ck_tile::index_t occupancy = (MaxK == 64) ? 3\n        : (MaxK >= 256)                                 ? 1\n                                                        : 2;\n\n    constexpr auto kBiasEnum = kHasBias\n        ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n        : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n    constexpr bool kPadSeqLenQ = true;\n    constexpr bool kPadSeqLenK = true;\n\n    const bool pad_headdim_q = !(param.K % FmhaFwdShape_::kSubQKHeaddim == 0);\n    const bool pad_headdim_v = !(param.Kv % FmhaFwdShape_::kN1 == 0);\n\n    BOOL_SWITCH_2(\n        pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n          using FmhaFwdTraits_ = ck_tile::TileFmhaTraits<\n              kPadSeqLenQ,\n              kPadSeqLenK,\n              kPadHeadDimQ,\n              kPadHeadDimV,\n              false, // kHasLogitsSoftCap\n              kBiasEnum,\n              false, // kHasBiasGrad place-holder\n              true, // kStoreLSE\n              kHasDropout,\n              false, // kDoFp8StaticQuant place-holder\n              occupancy>;\n\n          using FmhaPipelineProblem =\n              FmhaPipelineProblemTemp<FmhaFwdTraits_, FmhaMask>;\n\n          using FmhaFwdPipeline_ = std::conditional_t<\n              MaxK <= 256,\n              ck_tile::BlockFmhaPipelineQRKSVS<FmhaPipelineProblem>,\n              ck_tile::BlockFmhaPipelineQSKSVS<FmhaPipelineProblem>>;\n\n          using FmhaFwdEpilogue_ =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDimV>>;\n\n          using FmhaFwdKernel_ =\n              ck_tile::FmhaFwdKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n          RunWithKernel<FmhaFwdKernel_>(param, stream);\n        });\n  };\n\n  template <typename FmhaFwdKernel>\n  static void RunWithKernel(GroupedForwardParams& param, hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaFwdKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          nullptr, // rand_val_ptr\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.seqstart_q_dev_ptr,\n          param.seqstart_k_dev_ptr,\n          param.seqlen_k_dev_ptr,\n          param.K, // hdim_q\n          param.Kv, // hdim_v\n          param.Hq, // nhead_q\n          param.Hq / param.Hkv, // nhead_ratio_qk\n          param.scale,\n          1.0f, // scale_p\n          1.0f, // scale_o\n          0.0f, // logits_soft_cap\n          param.q_strides[0], // q, k, v, bias, randval, out tensor seq-dim\n                              // stride\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[2],\n          0, // stride_randval\n          param.out_strides[0],\n          param.q_strides[1], // q, k, v, bias, randval, lse, out tensor\n                              // head-dim stride\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[1],\n          0, // nhead_stride_randval\n          param.lse_strides[0],\n          param.out_strides[1],\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          0, // min_seqlen_q, most recently added kernel argument\n          param.dropout_prob,\n          false, // is_store_randval\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize = FmhaFwdKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.max_seqlen_q,\n        param.Kv,\n        param.seqlen_k_dev_ptr != nullptr);\n    constexpr dim3 kBlockSize = FmhaFwdKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_forward.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\n#include \"instances/fmha_grouped_forward_fp16_instances_ref.h\"\n\nvoid grouped_forward_fp16(GroupedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_grouped_forward_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_grouped_forward_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward_splitkv_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MaxSeqlenQ>\nstruct grouped_forward_splitkv_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type,\n          true, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          true, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n            using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ,\n                kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                true, // kStoreLSE\n                false, // kDoFp8StaticQuant place-holder\n                false, // kIsPagedKV\n                true, // kHasUnevenSplits\n                false, // kMergeNumHeadGroupsSeqLenQ\n                occupancy>;\n\n            if (param.num_kv_splits > 1) {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaFwdPipeline_ =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaFwdEpilogue_ =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaFwdKernel_ = ck_tile::\n                  FmhaFwdSplitKVKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n              RunWithFwdSplitKVKernel<FmhaFwdKernel_>(param, stream);\n            } else {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaFwdPipeline_ =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaFwdEpilogue_ =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaFwdKernel_ = ck_tile::\n                  FmhaFwdSplitKVKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n              RunWithFwdSplitKVKernel<FmhaFwdKernel_>(param, stream);\n            }\n          });\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr bool kPadSeqLenQ = true;\n\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH(pad_headdim_v, kPadHeadDimV, [&] {\n        FMHA_FWD_NUM_KV_SPLITS_SWITCH(param.num_kv_splits, kLogMaxSplits, [&] {\n          using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n              kPadSeqLenQ,\n              kPadHeadDimV,\n              true, // kStoreLSE\n              false, // kDoFp8StaticQuant place-holder\n              kLogMaxSplits,\n              -1>;\n\n          using FmhaPipelineProblem =\n              FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n          using FmhaPipeline =\n              ck_tile::BlockFmhaFwdSplitKVCombinePipeline<FmhaPipelineProblem>;\n\n          using FmhaEpilogue =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDimV>>;\n\n          using FmhaKernel =\n              ck_tile::FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n          RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n        });\n      });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr\n            0, // batch_stride_block_table\n            0, // page_block_size\n            false, // is_gappy\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[1],\n            param.q_strides[1], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[2],\n            0, // batch_stride_k, not used, only used for paged-kvcache\n            0, // batch_stride_v, not used, only used for paged-kvcache\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_ptr,\n            param.out_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr\n            0, // batch_stride_block_table\n            0, // page_block_size\n            false, // is_gappy\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_strides[0],\n            param.q_strides[1], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_strides[0],\n            param.out_strides[1],\n            0, // batch_stride_k, not used, only used for paged-kvcache\n            0, // batch_stride_v, not used, only used for paged-kvcache\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.Hkv,\n        param.max_seqlen_q,\n        param.Kv,\n        param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.num_batches,\n          param.seqstart_q_dev_ptr,\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[1], // row_stride_o_acc,\n          param.out_strides[0], // row_stride_o,\n          param.lse_acc_strides[1], // nhead_stride_lse_acc\n          param.out_acc_strides[2], // nhead_stride_o_acc,\n          param.lse_strides[0], // nhead_stride_lse,\n          param.out_strides[1], // nhead_stride_o,\n          param.lse_acc_strides[0], // split_stride_lse_acc,\n          param.out_acc_strides[0]); // split_stride_o_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_forward_splitkv_smallq_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK>\nstruct grouped_forward_splitkv_smallq_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVSmallQShape<MaxK>::Type,\n          true, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          true, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      const bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n      const bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n\n      BOOL_SWITCH_2(\n          pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n            using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ,\n                kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                true, // kStoreLSE\n                false, // kDoFp8StaticQuant place-holder\n                false, // kIsPagedKV\n                true, // kHasUnevenSplits\n                false, // kMergeNumHeadGroupsSeqLenQ\n                occupancy>;\n\n            if (param.num_kv_splits > 1) {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaFwdPipeline_ =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaFwdEpilogue_ =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaFwdKernel_ = ck_tile::\n                  FmhaFwdSplitKVKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n              RunWithFwdSplitKVKernel<FmhaFwdKernel_>(param, stream);\n            } else {\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaFwdPipeline_ =\n                  ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                      FmhaPipelineProblem>;\n\n              using FmhaFwdEpilogue_ =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaFwdKernel_ = ck_tile::\n                  FmhaFwdSplitKVKernel<FmhaFwdPipeline_, FmhaFwdEpilogue_>;\n\n              RunWithFwdSplitKVKernel<FmhaFwdKernel_>(param, stream);\n            }\n          });\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr bool kPadSeqLenQ = true;\n\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH(pad_headdim_v, kPadHeadDimV, [&] {\n        FMHA_FWD_NUM_KV_SPLITS_SWITCH(param.num_kv_splits, kLogMaxSplits, [&] {\n          using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n              kPadSeqLenQ,\n              kPadHeadDimV,\n              true, // kStoreLSE\n              false, // kDoFp8StaticQuant place-holder\n              kLogMaxSplits,\n              -1>;\n\n          using FmhaPipelineProblem =\n              FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n          using FmhaPipeline =\n              ck_tile::BlockFmhaFwdSplitKVCombinePipeline<FmhaPipelineProblem>;\n\n          using FmhaEpilogue =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDimV>>;\n\n          using FmhaKernel =\n              ck_tile::FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n          RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n        });\n      });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr\n            0, // batch_stride_block_table\n            0, // page_block_size\n            false, // is_gappy\n            param.scale,\n            1.0f, // scale_pz\n            0.f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[1],\n            param.q_strides[1], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[2],\n            0, // batch_stride_k, not used, only used for paged-kvcache\n            0, // batch_stride_v, not used, only used for paged-kvcache\n            param.lse_acc_strides[0], // split_stride_lse_acc\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_ptr,\n            param.out_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            nullptr, // block_table_ptr\n            0, // batch_stride_block_table\n            0, // page_block_size\n            false, // is_gappy\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out tensor seq-dim stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_strides[0],\n            param.q_strides[1], // q, k, v, bias, lse, out tensor head-dim\n                                // stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_strides[0],\n            param.out_strides[1],\n            0, // batch_stride_k, not used, only used for paged-kvcache\n            0, // batch_stride_v, not used, only used for paged-kvcache\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.Hkv,\n        param.max_seqlen_q,\n        param.Kv,\n        param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          param.logsumexp_ptr,\n          param.out_ptr,\n          param.num_batches,\n          param.seqstart_q_dev_ptr,\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[1], // row_stride_o_acc,\n          param.out_strides[0], // row_stride_o,\n          param.lse_acc_strides[1], // nhead_stride_lse_acc\n          param.out_acc_strides[2], // nhead_stride_o_acc,\n          param.lse_strides[0], // nhead_stride_lse,\n          param.out_strides[1], // nhead_stride_o,\n          param.lse_acc_strides[0], // split_stride_lse_acc,\n          param.out_acc_strides[0]); // split_stride_o_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <algorithm>\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_selector.h\"\n#include \"ck_tiled_fmha_grouped_infer_dispatch.h\"\n#include \"ck_tiled_fmha_grouped_infer_splitkv_dispatch.h\"\n#include \"ck_tiled_fmha_grouped_infer_splitkv_smallq_dispatch.h\"\n#include \"ck_tiled_fmha_seqlen_q_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK>\nvoid run_grouped_infer_mask_bias_dropout_dispatch(\n    GroupedForwardParams& param,\n    hipStream_t stream) {\n  // currently split-kv implementation does not support:\n  // (*) dropout\n  // (*) head dimension > 256\n  if constexpr (!kHasDropout) {\n    if (param.use_split_kv && MaxK <= 256) {\n      if constexpr (MaxK <= 256) {\n        if (use_splitkv_smallq(\n                param.max_seqlen_q, std::max(param.K, param.Kv))) {\n          grouped_infer_splitkv_smallq_mask_bias_dropout_dispatch<\n              ScalarType,\n              kHasMask,\n              kHasBias,\n              MaxK>::Run(param, stream);\n        } else {\n          FMHA_FWD_SEQLEN_Q_SWITCH(param.max_seqlen_q, MaxSeqlenQ, [&] {\n            grouped_infer_splitkv_mask_bias_dropout_dispatch<\n                ScalarType,\n                kHasMask,\n                kHasBias,\n                MaxK,\n                MaxSeqlenQ>::Run(param, stream);\n          });\n        }\n      } else {\n        // Unreachable. Do not instantiate split-kv pipelines with head\n        // dimension > 256\n      }\n    } else {\n      const auto mtile =\n          get_fmha_fwd_mtile(param.num_batches, param.Hq, param.max_seqlen_q);\n\n      if (mtile == 128)\n        grouped_infer_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            128>::Run(param, stream);\n      else\n        grouped_infer_mask_bias_dropout_dispatch<\n            ScalarType,\n            kHasMask,\n            kHasBias,\n            kHasDropout,\n            MaxK,\n            64>::Run(param, stream);\n    }\n  } else {\n    // at present, dropout of fwd kernel requires 32x32 WarpTile\n    grouped_infer_mask_bias_dropout_dispatch<\n        ScalarType,\n        kHasMask,\n        kHasBias,\n        kHasDropout,\n        MaxK,\n        128>::Run(param, stream);\n  }\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer_bf16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\n#include \"instances/fmha_grouped_infer_bf16_instances_ref.h\"\n\nvoid grouped_infer_bf16(GroupedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_grouped_infer_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_grouped_infer_mask_bias_dropout_dispatch<\n            ck_tile::bf16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_setting.h\"\n#include \"ck_tiled_fmha_params.h\"\n#include \"ck_tiled_headdim_switch.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    bool kHasDropout,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MTile>\nstruct grouped_infer_mask_bias_dropout_dispatch {\n  static constexpr bool kUseWholeKPrefetchPipeline =\n      (MaxK <= 128 && !kHasDropout);\n\n  using FmhaShape = typename FmhaFwdShape<MaxK, MTile>::Type;\n\n  static constexpr ck_tile::index_t kKLoadLength =\n      (kUseWholeKPrefetchPipeline || MaxK > 256) ? FmhaShape::kQKHeaddim\n                                                 : FmhaShape::kSubQKHeaddim;\n\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <typename FmhaTraits, typename FmhaMask>\n  using FmhaPipelineProblemTemp = ck_tile::BlockFmhaPipelineProblem<\n      typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::RandValOutputDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n      typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n      FmhaShape,\n      true, // kIsGroupMode\n      AttentionVariant<FmhaTraits>,\n      FmhaMask,\n      FmhaTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n    constexpr ck_tile::index_t occupancy = -1;\n\n    constexpr auto kBiasEnum = kHasBias\n        ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n        : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n    // no need to check seqlen_q since it is not used as fastest dim,\n    // buffer_load_dwordxx/buffer_store_dwordxx can handle oob access\n    constexpr bool kPadSeqLenQ = false;\n    constexpr bool kPadSeqLenK = true;\n\n    bool pad_headdim_q = !(param.K % kKLoadLength == 0);\n    bool pad_headdim_v = !(param.Kv % FmhaShape::kN1 == 0);\n\n    // only use qr_ks_vs_async pipeline with hdim-96\n    const bool use_async_pipeline =\n        (!kHasBias && (param.K % 8 == 0) && (param.Kv % 8 == 0) &&\n         (MaxK <= 128 && MTile == 128));\n\n    if (!use_async_pipeline) {\n      BOOL_SWITCH_2(\n          pad_headdim_q, kPadHeadDimQ, pad_headdim_v, kPadHeadDimV, [&] {\n            using FmhaTraits = ck_tile::TileFmhaTraits<\n                kPadSeqLenQ,\n                kPadSeqLenK,\n                kPadHeadDimQ,\n                kPadHeadDimV,\n                false, // kHasLogitsSoftCap\n                kBiasEnum,\n                false, // kHasBiasGrad place-holder\n                false, // kStoreLSE\n                kHasDropout,\n                false, // kDoFp8StaticQuant place-holder\n                occupancy>;\n\n            using FmhaPipelineProblem =\n                FmhaPipelineProblemTemp<FmhaTraits, FmhaMask>;\n\n            using FmhaEpilogue =\n                ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                    kPadSeqLenQ,\n                    kPadHeadDimV>>;\n\n            if constexpr (kUseWholeKPrefetchPipeline) {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQRKSVSWholeKPrefetch<\n                      FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            } else if constexpr (MaxK <= 256) {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQRKSVS<FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            } else {\n              using FmhaPipeline =\n                  ck_tile::BlockFmhaPipelineQSKSVS<FmhaPipelineProblem>;\n              using FmhaKernel =\n                  ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithKernel<FmhaKernel>(param, stream);\n            }\n          });\n    } else {\n      if constexpr (MaxK <= 128 && MTile == 128) {\n        using FmhaTraits = ck_tile::TileFmhaTraits<\n            true, // kPadSeqLenQ,\n            kPadSeqLenK,\n            true, // kPadHeadDimQ,\n            true, // kPadHeadDimV,\n            false, // kHasLogitsSoftCap\n            kBiasEnum,\n            false, // kHasBiasGrad place-holder\n            false, // kStoreLSE\n            kHasDropout,\n            false, // kDoFp8StaticQuant place-holder\n            occupancy>;\n\n        using FmhaPipelineProblem =\n            FmhaPipelineProblemTemp<FmhaTraits, FmhaMask>;\n\n        using FmhaPipeline =\n            ck_tile::BlockFmhaPipelineQRKSVSAsync<FmhaPipelineProblem>;\n\n        using FmhaEpilogue =\n            ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                true,\n                true>>;\n\n        using FmhaKernel = ck_tile::FmhaFwdKernel<FmhaPipeline, FmhaEpilogue>;\n\n        RunWithKernel<FmhaKernel>(param, stream);\n      } else {\n        /* runtime will never get here, so no codes to compile */\n      };\n    };\n  };\n\n  template <typename FmhaKernel>\n  static void RunWithKernel(GroupedForwardParams& param, hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaKernel::MakeKargs(\n          param.q_ptr,\n          param.k_ptr,\n          param.v_ptr,\n          param.attn_bias_ptr,\n          nullptr, // rand_val_ptr\n          nullptr, // lse_ptr\n          param.out_ptr,\n          param.seqstart_q_dev_ptr,\n          param.seqstart_k_dev_ptr,\n          param.seqlen_k_dev_ptr,\n          param.K, // hdim_q\n          param.Kv, // hdim_v\n          param.Hq, // nhead_q\n          param.Hq / param.Hkv, // nhead_ratio_qk\n          param.scale,\n          1.0f, // scale_p\n          1.0f, // scale_o\n          0.f, // logits_soft_cap\n          param.q_strides[0], // q, k, v, bias, randval, out tensor seq-dim\n                              // stride\n          param.k_strides[0],\n          param.v_strides[0],\n          param.attn_bias_strides[2],\n          0, // stride_randval\n          param.out_strides[0],\n          param.q_strides[1], // q, k, v, bias, randval, lse, out tensor\n                              // head-dim stride\n          param.k_strides[1],\n          param.v_strides[1],\n          param.attn_bias_strides[1],\n          0, // nhead_stride_randval\n          0, // nhead_stride_lse\n          param.out_strides[1],\n          (param.window_size > 0) ? param.window_size - 1\n                                  : -1, // window_left_size\n          (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n          param.custom_mask_type,\n          0, // min_seqlen_q, most recently added kernel argument\n          param.dropout_prob,\n          false, // is_store_randval\n          std::make_pair(param.philox_seed, param.philox_offset));\n    }();\n\n    dim3 kGridSize = FmhaKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.max_seqlen_q,\n        param.Kv,\n        param.seqlen_k_dev_ptr != nullptr);\n    constexpr dim3 kBlockSize = FmhaKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer_fp16.cpp",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\n#include \"instances/fmha_grouped_infer_fp16_instances_ref.h\"\n\nvoid grouped_infer_fp16(GroupedForwardParams& param, hipStream_t stream) {\n  const bool has_dropout = (param.dropout_prob > 0.0f);\n  BOOL_SWITCH_2(param.has_attn_bias, kHasBias, has_dropout, kHasDropout, [&] {\n    FMHA_FWD_HEADDIM_SWITCH(param.K, param.Kv, MaxK, [&] {\n      if (param.custom_mask_type == 0 && param.window_size <= 0)\n        run_grouped_infer_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            false,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else if (\n          param.custom_mask_type == 1 || param.custom_mask_type == 2 ||\n          param.window_size > 0)\n        run_grouped_infer_mask_bias_dropout_dispatch<\n            ck_tile::fp16_t,\n            true,\n            kHasBias,\n            kHasDropout,\n            MaxK>(param, stream);\n      else\n        throw std::runtime_error(\"Invalid custom_mask_type value\");\n    });\n  });\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer_splitkv_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK,\n    ck_tile::index_t MaxSeqlenQ>\nstruct grouped_infer_splitkv_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type,\n          true, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          true, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n      bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n\n      bool is_paged_kv = param.use_paged_kvcache;\n\n      BOOL_SWITCH_3(\n          pad_headdim_q,\n          kPadHeadDimQ,\n          pad_headdim_v,\n          kPadHeadDimV,\n          is_paged_kv,\n          kIsPagedKV,\n          [&] {\n            if (param.num_kv_splits > 1) {\n              using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                  kPadSeqLenQ,\n                  kPadSeqLenK,\n                  kPadHeadDimQ,\n                  kPadHeadDimV,\n                  false, // kLogitsSoftCap\n                  kBiasEnum,\n                  false, // kHasBiasGrad place-holder\n                  true, // kStoreLSE\n                  false, // kDoFp8StaticQuant place-holder\n                  kIsPagedKV,\n                  true, // kHasUnevenSplits\n                  false, // kMergeNumHeadGroupsSeqLenQ\n                  occupancy>;\n\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            } else {\n              using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                  kPadSeqLenQ,\n                  kPadSeqLenK,\n                  kPadHeadDimQ,\n                  kPadHeadDimV,\n                  false, // kLogitsSoftCap\n                  kBiasEnum,\n                  false, // kHasBiasGrad place-holder\n                  false, // kStoreLSE\n                  false, // kDoFp8StaticQuant place-holder\n                  kIsPagedKV,\n                  true, // kHasUnevenSplits\n                  false, // kMergeNumHeadGroupsSeqLenQ\n                  occupancy>;\n\n              using ODataType =\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n              using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                  FmhaTraits,\n                  FmhaMask,\n                  ODataType>;\n\n              using FmhaPipeline = ck_tile::BlockFmhaFwdSplitKVPipelineQRKSVS<\n                  FmhaPipelineProblem>;\n\n              using FmhaEpilogue =\n                  ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                      typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                      ODataType,\n                      false,\n                      false>>;\n\n              using FmhaKernel =\n                  ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n              RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n            }\n          });\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape =\n          typename FmhaFwdSplitKVShape<MaxK, MaxSeqlenQ>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr bool kPadSeqLenQ = true;\n\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH(pad_headdim_v, kPadHeadDimV, [&] {\n        FMHA_FWD_NUM_KV_SPLITS_SWITCH(param.num_kv_splits, kLogMaxSplits, [&] {\n          using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n              kPadSeqLenQ,\n              kPadHeadDimV,\n              false, // kStoreLSE\n              false, // kDoFp8StaticQuant place-holder\n              kLogMaxSplits,\n              -1>;\n\n          using FmhaPipelineProblem =\n              FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n          using FmhaPipeline =\n              ck_tile::BlockFmhaFwdSplitKVCombinePipeline<FmhaPipelineProblem>;\n\n          using FmhaEpilogue =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDimV>>;\n\n          using FmhaKernel =\n              ck_tile::FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n          RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n        });\n      });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            param.use_paged_kvcache ? param.block_table_ptr : nullptr,\n            param.use_paged_kvcache ? param.batch_stride_block_table : 0,\n            param.use_paged_kvcache ? param.page_block_size : 0,\n            param.use_paged_kvcache ? param.is_gappy : false,\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[1],\n            param.q_strides[1], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[2],\n            param.use_paged_kvcache ? param.k_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_k\n            param.use_paged_kvcache ? param.v_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_v\n            param.lse_acc_strides[0], // split_stride_l\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            nullptr, // lse_ptr,\n            param.out_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            param.use_paged_kvcache ? param.block_table_ptr : nullptr,\n            param.use_paged_kvcache ? param.batch_stride_block_table : 0,\n            param.use_paged_kvcache ? param.page_block_size : 0,\n            param.use_paged_kvcache ? param.is_gappy : false,\n            param.scale,\n            1.0f, // scale_p\n            0.0f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_strides[0],\n            param.q_strides[1], // q, k, v, bias, lse, out tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            0, // nhead_stride_lse\n            param.out_strides[1],\n            param.use_paged_kvcache ? param.k_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_k\n            param.use_paged_kvcache ? param.v_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_v\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.Hkv,\n        param.max_seqlen_q,\n        param.Kv,\n        param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          nullptr, // lse_ptr, not used\n          param.out_ptr,\n          param.num_batches,\n          param.seqstart_q_dev_ptr,\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[1], // row_stride_o_acc,\n          param.out_strides[0], // row_stride_o,\n          param.lse_acc_strides[1], // nhead_stride_lse_acc\n          param.out_acc_strides[2], // nhead_stride_o_acc,\n          0, // nhead_stride_lse,\n          param.out_strides[1], // nhead_stride_o,\n          param.lse_acc_strides[0], // split_stride_lse_acc,\n          param.out_acc_strides[0]); // split_stride_o_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_grouped_infer_splitkv_smallq_dispatch.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core/numeric/integer.hpp>\n#include <ck_tile/host/kernel_launch.hpp>\n#include <ck_tile/host/stream_config.hpp>\n#include <ck_tile/ops/epilogue.hpp>\n#include <ck_tile/ops/fmha.hpp>\n\n#include \"ck_tiled_bool_switch.h\"\n#include \"ck_tiled_fmha_fwd_splitkv_smallq_setting.h\"\n#include \"ck_tiled_fmha_num_kv_split_switch.h\"\n#include \"ck_tiled_fmha_params.h\"\n\ntemplate <\n    typename ScalarType,\n    bool kHasMask,\n    bool kHasBias,\n    ck_tile::index_t MaxK>\nstruct grouped_infer_splitkv_smallq_mask_bias_dropout_dispatch {\n  template <typename FmhaTraits>\n  using AttentionVariant = ck_tile::ComposedAttention<\n      FmhaTraits::kHasLogitsSoftCap * ck_tile::LOGITS_SOFT_CAP,\n      CK_TILE_FMHA_FWD_FAST_EXP2>;\n  template <\n      typename FmhaFwdSplitKVTraits,\n      typename FmhaMask,\n      typename ODataType>\n  using FmhaFwdSplitKVPipelineProblemTemp =\n      ck_tile::BlockFmhaFwdSplitKVPipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::QDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::KDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::VDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::SMPLComputeDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::BiasDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::PDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          ODataType,\n          typename FmhaFwdSplitKVSmallQShape<MaxK>::Type,\n          true, // kIsGroupMode\n          AttentionVariant<FmhaFwdSplitKVTraits>,\n          FmhaMask,\n          FmhaFwdSplitKVTraits>;\n\n  template <ck_tile::index_t kN1, typename FmhaSplitKVCombineTraits>\n  using FmhaSplitKVCombinePipelineProblemTemp =\n      ck_tile::BlockFmhaSplitKVCombinePipelineProblem<\n          typename FmhaFwdTypeConfig<ScalarType>::LSEDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n          typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n          MaxK, // headdim_v\n          true, // kIsGroupMode\n          kN1,\n          FmhaSplitKVCombineTraits>;\n\n  static void Run(GroupedForwardParams& param, hipStream_t stream) {\n    {\n      using FmhaMask = ck_tile::SimplifiedGenericAttentionMask<kHasMask>;\n\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr auto kBiasEnum = kHasBias\n          ? ck_tile::BlockAttentionBiasEnum::ELEMENTWISE_BIAS\n          : ck_tile::BlockAttentionBiasEnum::NO_BIAS;\n\n      constexpr bool kPadSeqLenQ = true;\n      constexpr bool kPadSeqLenK = true;\n\n      bool pad_headdim_q = !(param.K % FmhaTileShape::kSubQKHeaddim == 0);\n      bool pad_headdim_v = !(param.Kv % FmhaTileShape::kN1 == 0);\n\n      bool is_paged_kv = param.use_paged_kvcache;\n\n      // indicates to the splitkv kernel whether should it merge Hq/Hkv with\n      // seqlen_q\n      const bool merge_nhead_groups_seqlen_q =\n          ((param.max_seqlen_q == 1) && (param.Hq > param.Hkv) && !kHasBias &&\n           !kHasMask);\n\n      if (merge_nhead_groups_seqlen_q) {\n        using FmhaMaskNone = ck_tile::SimplifiedGenericAttentionMask<false>;\n        BOOL_SWITCH_3(\n            pad_headdim_q,\n            kPadHeadDimQ,\n            pad_headdim_v,\n            kPadHeadDimV,\n            is_paged_kv,\n            kIsPagedKV,\n            [&] {\n              if (param.num_kv_splits > 1) {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDimQ,\n                    kPadHeadDimV,\n                    false, // kLogitsSoftCap\n                    ck_tile::BlockAttentionBiasEnum::NO_BIAS,\n                    false, // kHasBiasGrad place-holder\n                    true, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    kIsPagedKV,\n                    true, // kHasUnevenSplits\n                    true, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMaskNone,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              } else {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDimQ,\n                    kPadHeadDimV,\n                    false, // kLogitsSoftCap\n                    ck_tile::BlockAttentionBiasEnum::NO_BIAS,\n                    false, // kHasBiasGrad place-holder\n                    false, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    kIsPagedKV,\n                    true, // kHasUnevenSplits\n                    true, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMaskNone,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              }\n            });\n      } else {\n        BOOL_SWITCH_3(\n            pad_headdim_q,\n            kPadHeadDimQ,\n            pad_headdim_v,\n            kPadHeadDimV,\n            is_paged_kv,\n            kIsPagedKV,\n            [&] {\n              if (param.num_kv_splits > 1) {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDimQ,\n                    kPadHeadDimV,\n                    false, // kLogitsSoftCap\n                    kBiasEnum,\n                    false, // kHasBiasGrad place-holder\n                    true, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    kIsPagedKV,\n                    true, // kHasUnevenSplits\n                    false, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::OaccDataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMask,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              } else {\n                using FmhaTraits = ck_tile::TileFmhaFwdSplitKVTraits<\n                    kPadSeqLenQ,\n                    kPadSeqLenK,\n                    kPadHeadDimQ,\n                    kPadHeadDimV,\n                    false, // kLogitsSoftCap\n                    kBiasEnum,\n                    false, // kHasBiasGrad place-holder\n                    false, // kStoreLSE\n                    false, // kDoFp8StaticQuant place-holder\n                    kIsPagedKV,\n                    true, // kHasUnevenSplits\n                    false, // kMergeNumHeadGroupsSeqLenQ\n                    occupancy>;\n\n                using ODataType =\n                    typename FmhaFwdTypeConfig<ScalarType>::ODataType;\n                using FmhaPipelineProblem = FmhaFwdSplitKVPipelineProblemTemp<\n                    FmhaTraits,\n                    FmhaMask,\n                    ODataType>;\n\n                using FmhaPipeline =\n                    ck_tile::BlockFmhaFwdSplitKVPipelineNWarpSShuffleQRKSVS<\n                        FmhaPipelineProblem>;\n\n                using FmhaEpilogue = ck_tile::Default2DEpilogue<\n                    ck_tile::Default2DEpilogueProblem<\n                        typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                        ODataType,\n                        false,\n                        false>>;\n\n                using FmhaKernel =\n                    ck_tile::FmhaFwdSplitKVKernel<FmhaPipeline, FmhaEpilogue>;\n\n                RunWithFwdSplitKVKernel<FmhaKernel>(param, stream);\n              }\n            });\n      };\n    };\n\n    if (param.num_kv_splits > 1) {\n      using FmhaTileShape = typename FmhaFwdSplitKVSmallQShape<MaxK>::Type;\n\n      constexpr ck_tile::index_t kN1 = 32;\n      constexpr ck_tile::index_t kM0 =\n          ck_tile::BlockFmhaSplitKVCombinePipelineTileSizes<\n              typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n              kN1>::kM0;\n\n      constexpr ck_tile::index_t occupancy = -1;\n\n      constexpr bool kPadSeqLenQ = true;\n\n      const bool pad_headdim_v = !(param.Kv % kN1 == 0);\n\n      BOOL_SWITCH(pad_headdim_v, kPadHeadDimV, [&] {\n        FMHA_FWD_NUM_KV_SPLITS_SWITCH(param.num_kv_splits, kLogMaxSplits, [&] {\n          using FmhaTraits = ck_tile::TileFmhaFwdSplitKVCombineTraits<\n              kPadSeqLenQ,\n              kPadHeadDimV,\n              false, // kStoreLSE\n              false, // kDoFp8StaticQuant place-holder\n              kLogMaxSplits,\n              -1>;\n\n          using FmhaPipelineProblem =\n              FmhaSplitKVCombinePipelineProblemTemp<kN1, FmhaTraits>;\n\n          using FmhaPipeline =\n              ck_tile::BlockFmhaFwdSplitKVCombinePipeline<FmhaPipelineProblem>;\n\n          using FmhaEpilogue =\n              ck_tile::Default2DEpilogue<ck_tile::Default2DEpilogueProblem<\n                  typename FmhaFwdTypeConfig<ScalarType>::OaccDataType,\n                  typename FmhaFwdTypeConfig<ScalarType>::ODataType,\n                  kPadSeqLenQ,\n                  kPadHeadDimV>>;\n\n          using FmhaKernel =\n              ck_tile::FmhaFwdSplitKVCombineKernel<FmhaPipeline, FmhaEpilogue>;\n\n          RunWithSplitKVCombineKernel<FmhaKernel>(param, stream);\n        });\n      });\n    };\n  };\n\n  template <typename FmhaFwdSplitKVKernel>\n  static void RunWithFwdSplitKVKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      if (param.num_kv_splits > 1)\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            param.logsumexp_acc_ptr,\n            param.out_acc_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            param.use_paged_kvcache ? param.block_table_ptr : nullptr,\n            param.use_paged_kvcache ? param.batch_stride_block_table : 0,\n            param.use_paged_kvcache ? param.page_block_size : 0,\n            param.use_paged_kvcache ? param.is_gappy : false,\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out_acc tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_acc_strides[1],\n            param.q_strides[1], // q, k, v, bias, lse_acc, out_acc tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            param.lse_acc_strides[1],\n            param.out_acc_strides[2],\n            param.use_paged_kvcache ? param.k_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_k\n            param.use_paged_kvcache ? param.v_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_v\n            param.lse_acc_strides[0], // split_stride_l\n            param.out_acc_strides[0], // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n      else\n        return FmhaFwdSplitKVKernel::MakeKargs(\n            param.q_ptr,\n            param.k_ptr,\n            param.v_ptr,\n            param.attn_bias_ptr,\n            nullptr, // lse_ptr,\n            param.out_ptr,\n            param.num_batches,\n            param.seqstart_q_dev_ptr,\n            param.seqstart_k_dev_ptr,\n            param.seqlen_k_dev_ptr,\n            param.K, // hdim_q\n            param.Kv, // hdim_v\n            param.Hq, // nhead_q\n            param.Hq / param.Hkv, // nhead_ratio_qk\n            param.num_kv_splits, // num_splits\n            param.use_paged_kvcache ? param.block_table_ptr : nullptr,\n            param.use_paged_kvcache ? param.batch_stride_block_table : 0,\n            param.use_paged_kvcache ? param.page_block_size : 0,\n            param.use_paged_kvcache ? param.is_gappy : false,\n            param.scale,\n            1.0f, // scale_p\n            0.f, // logits_soft_cap\n            param.q_strides[0], // q, k, v, bias, out tensor seq-dim\n                                // stride\n            param.k_strides[0],\n            param.v_strides[0],\n            param.attn_bias_strides[2],\n            param.out_strides[0],\n            param.q_strides[1], // q, k, v, bias, lse, out tensor\n                                // head-dim stride\n            param.k_strides[1],\n            param.v_strides[1],\n            param.attn_bias_strides[1],\n            0, // nhead_stride_lse\n            param.out_strides[1],\n            param.use_paged_kvcache ? param.k_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_k\n            param.use_paged_kvcache ? param.v_strides[0] * param.page_block_size\n                                    : 0, // batch_stride_v\n            0, // split_stride_lse_acc\n            0, // split_stride_out_acc\n            (param.window_size > 0) ? param.window_size - 1\n                                    : -1, // window_left_size\n            (param.custom_mask_type == 0) ? -1 : 0, // window_right_size\n            param.custom_mask_type);\n    }();\n\n    dim3 kGridSize = FmhaFwdSplitKVKernel::GridSize(\n        param.num_batches,\n        param.Hq,\n        param.Hkv,\n        param.max_seqlen_q,\n        param.Kv,\n        param.num_kv_splits);\n    constexpr dim3 kBlockSize = FmhaFwdSplitKVKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu = FmhaFwdSplitKVKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaFwdSplitKVKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n\n  template <typename FmhaSplitKVCombineKernel>\n  static void RunWithSplitKVCombineKernel(\n      GroupedForwardParams& param,\n      hipStream_t stream) {\n    const auto kargs = [&] {\n      return FmhaSplitKVCombineKernel::MakeKargs(\n          param.logsumexp_acc_ptr,\n          param.out_acc_ptr,\n          nullptr, // lse_ptr, not used\n          param.out_ptr,\n          param.num_batches,\n          param.seqstart_q_dev_ptr,\n          param.Kv,\n          param.num_kv_splits,\n          1.0f,\n          param.out_acc_strides[1], // row_stride_o_acc,\n          param.out_strides[0], // row_stride_o,\n          param.lse_acc_strides[1], // nhead_stride_lse_acc\n          param.out_acc_strides[2], // nhead_stride_o_acc,\n          0, // nhead_stride_lse,\n          param.out_strides[1], // nhead_stride_o,\n          param.lse_acc_strides[0], // split_stride_lse_acc,\n          param.out_acc_strides[0]); // split_stride_o_acc\n    }();\n\n    dim3 kGridSize = FmhaSplitKVCombineKernel::GridSize(\n        param.num_batches, param.Hq, param.max_seqlen_q, param.Kv);\n    constexpr dim3 kBlockSize = FmhaSplitKVCombineKernel::BlockSize();\n    constexpr ck_tile::index_t kBlockPerCu =\n        FmhaSplitKVCombineKernel::kBlockPerCu;\n\n    (void)ck_tile::launch_kernel(\n        ck_tile::stream_config{stream, false},\n        ck_tile::make_kernel<kBlockSize.x, kBlockPerCu>(\n            FmhaSplitKVCombineKernel{}, kGridSize, kBlockSize, 0, kargs));\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_num_kv_split_switch.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#define FMHA_FWD_NUM_KV_SPLITS_SWITCH(NUM_SPLITS, CONST_NAME, ...) \\\n  [&] {                                                            \\\n    if (NUM_SPLITS <= 8) {                                         \\\n      constexpr ck_tile::index_t CONST_NAME = 3;                   \\\n      __VA_ARGS__();                                               \\\n    } else if (NUM_SPLITS <= 16) {                                 \\\n      constexpr ck_tile::index_t CONST_NAME = 4;                   \\\n      __VA_ARGS__();                                               \\\n    } else {                                                       \\\n      throw std::runtime_error(\"num-splits not supported!\");       \\\n    }                                                              \\\n  }()\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_params.h",
    "content": "/*\n * Copyright (c) 2023, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <array>\n#include <cstdint>\n\nstruct BatchedInferParams {\n  int B; // batch size\n  int M; // seq_len for Query\n  int N; // seq_len for Key and Value\n  int Hq; // number of heads for Query\n  int Hkv; // number of heads for Key and Value\n  int K; // embed_dim for Query and Key\n  int Kv; // embed_dim for Value\n\n  float scale;\n  bool has_attn_bias;\n\n  // BMHK mode strides\n  std::array<int, 4> q_strides;\n  std::array<int, 4> k_strides;\n  std::array<int, 4> v_strides;\n  std::array<int, 4> out_strides;\n  std::array<int, 4> attn_bias_strides; // 4d tensor_view [B, H, M, N]\n\n  const void* q_ptr;\n  const void* k_ptr;\n  const void* v_ptr;\n  const void* attn_bias_ptr;\n\n  int custom_mask_type;\n  int window_size; // local-attention\n\n  void* out_ptr;\n};\n\nstruct BatchedForwardParams : public BatchedInferParams {\n  bool compute_logsumexp;\n\n  float dropout_prob;\n  int64_t philox_seed;\n  int64_t philox_offset;\n\n  // BHM mode strides, completely contiguous\n  std::array<int, 3> lse_strides;\n\n  // completely contiguous\n  void* logsumexp_ptr;\n\n  // used by the splitkv forward kernel\n  int num_kv_splits;\n\n  bool use_split_kv;\n\n  // PBHM mode strides, completely contiguous\n  std::array<int, 4> lse_acc_strides;\n\n  // PBMHK mode strides\n  std::array<int, 5> out_acc_strides;\n\n  void* logsumexp_acc_ptr;\n  void* out_acc_ptr;\n};\n\nstruct GroupedInferParams {\n  int num_batches;\n  int M; // total seq_len for all queries in the batch\n  int N; // total seq_len for all keys/values in the batch\n  int Hq; // number of heads for Query\n  int Hkv; // number of heads for Key and Value\n  int K; // embed_dim for Query and Key\n  int Kv; // embed_dim for Value\n\n  int max_seqlen_q;\n\n  void* seqstart_q_dev_ptr;\n  void* seqstart_k_dev_ptr;\n  void* seqlen_k_dev_ptr;\n\n  float scale;\n  bool has_attn_bias;\n\n  // MHK mode strides, last-dim contiguous\n  std::array<int, 3> q_strides;\n  std::array<int, 3> k_strides;\n  std::array<int, 3> v_strides;\n  std::array<int, 3> out_strides;\n\n  // 4d tensor view [B, H, M, N]\n  std::array<int, 4> attn_bias_strides;\n\n  const void* q_ptr;\n  const void* k_ptr;\n  const void* v_ptr;\n  const void* attn_bias_ptr;\n\n  int custom_mask_type;\n  int window_size; // local-attention\n\n  void* out_ptr;\n\n  bool use_paged_kvcache;\n  bool is_gappy;\n  void* block_table_ptr;\n  int page_block_size;\n  int batch_stride_block_table;\n};\n\nstruct GroupedForwardParams : public GroupedInferParams {\n  bool compute_logsumexp;\n\n  float dropout_prob;\n  int64_t philox_seed;\n  int64_t philox_offset;\n\n  // HM mode strides, completely contiguous, unpadded layout where M is\n  // concatten total seqlen_q for all batches\n  std::array<int, 2> lse_strides;\n\n  // completely contiguous\n  void* logsumexp_ptr;\n\n  // used by the splitkv forward kernel\n  int num_kv_splits;\n\n  bool use_split_kv;\n\n  // PHM mode strides, completely contiguous, unpadded layout where M is\n  // concatten total seqlen_q for all batches\n  std::array<int, 3> lse_acc_strides;\n\n  // PMHK mode strides, last-dim contiguous\n  std::array<int, 4> out_acc_strides;\n\n  void* logsumexp_acc_ptr;\n  void* out_acc_ptr;\n};\n\nstruct BatchedBackwardParams {\n  int B; // batch size\n  int M; // seq_len for Query\n  int N; // seq_len for Key and Value\n  int Hq; // number of heads for Query\n  int Hkv; // number of heads for Key and Value\n  int K; // embed_dim for Query and Key\n  int Kv; // embed_dim for Value\n\n  float scale;\n  bool has_attn_bias;\n  bool bias_has_grad;\n\n  bool is_mqa_gqa;\n\n  // BMHK mode strides, last-dim contiguous\n  std::array<int, 4> q_strides;\n  std::array<int, 4> k_strides;\n  std::array<int, 4> v_strides;\n  std::array<int, 4> attn_bias_strides; // 4d tensor_view [B, H, M, N]\n  std::array<int, 4> out_strides;\n  std::array<int, 4> grad_out_strides;\n\n  std::array<int, 4> grad_k_strides;\n  std::array<int, 4> grad_v_strides;\n\n  // assume grad_q has same strides as q, but grad_q_f32 can be different\n  std::array<int, 4> grad_q_f32_strides;\n\n  // BHM mode strides, completely contiguous\n  std::array<int, 3> lsed_strides;\n\n  const void* q_ptr;\n  const void* k_ptr;\n  const void* v_ptr;\n  const void* attn_bias_ptr;\n  const void* grad_out_ptr;\n  const void* out_ptr;\n\n  uint8_t custom_mask_type;\n  int window_size; // local-attention\n\n  void* grad_q_ptr;\n  void* grad_k_ptr;\n  void* grad_v_ptr;\n  void* grad_bias_ptr;\n\n  void* grad_q_f32_ptr;\n\n  float dropout_prob;\n  int64_t philox_seed;\n  int64_t philox_offset;\n\n  // BHM mode lengths, completely contiguous\n  const void* logsumexp_ptr;\n  void* dot_out_ptr;\n};\n\nstruct GroupedBackwardParams {\n  int num_batches;\n  int M; // total seq_len for all queries in the batch\n  int N; // total seq_len for all keys/values in the batch\n  int Hq; // number of heads for Query\n  int Hkv; // number of heads for Key and Value\n  int K; // embed_dim for Query and Key\n  int Kv; // embed_dim for Value\n\n  int max_seqlen_q;\n  int max_seqlen_k;\n\n  void* seqstart_q_dev_ptr;\n  void* seqstart_k_dev_ptr;\n  void* seqlen_k_dev_ptr;\n\n  float scale;\n  bool has_attn_bias;\n  bool bias_has_grad;\n\n  bool is_mqa_gqa;\n\n  // MHK mode strides, last-dim contiguous\n  std::array<int, 3> q_strides;\n  std::array<int, 3> k_strides;\n  std::array<int, 3> v_strides;\n  std::array<int, 3> out_strides;\n  std::array<int, 3> grad_out_strides;\n  // 4d tensor view [B, H, M, N]\n  std::array<int, 4> attn_bias_strides;\n\n  std::array<int, 3> grad_k_strides;\n  std::array<int, 3> grad_v_strides;\n\n  // assume grad_q has same strides as q, but grad_q_f32 can be different\n  std::array<int, 3> grad_q_f32_strides;\n\n  // HM mode strides, completely contiguous, unpadded layout where M is\n  // concatten total seqlen_q for all batches\n  std::array<int, 2> lsed_strides;\n\n  const void* q_ptr;\n  const void* k_ptr;\n  const void* v_ptr;\n  const void* attn_bias_ptr;\n  const void* grad_out_ptr;\n  const void* out_ptr;\n\n  uint8_t custom_mask_type;\n  int window_size; // local-attention\n\n  void* grad_q_ptr;\n  void* grad_k_ptr;\n  void* grad_v_ptr;\n  void* grad_bias_ptr;\n\n  void* grad_q_f32_ptr;\n\n  float dropout_prob;\n  int64_t philox_seed;\n  int64_t philox_offset;\n\n  // BHM mode lengths, completely contiguous\n  const void* logsumexp_ptr;\n  void* dot_out_ptr;\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_fmha_seqlen_q_switch.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#define FMHA_FWD_SEQLEN_Q_SWITCH(SEQLEN_Q, CONST_NAME, ...) \\\n  [&] {                                                     \\\n    if (SEQLEN_Q <= 32) {                                   \\\n      constexpr ck_tile::index_t CONST_NAME = 32;           \\\n      __VA_ARGS__();                                        \\\n    } else {                                                \\\n      constexpr ck_tile::index_t CONST_NAME = 64;           \\\n      __VA_ARGS__();                                        \\\n    }                                                       \\\n  }()\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_headdim_switch.h",
    "content": "/*\n * Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n */\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <stdexcept>\n\n#ifndef FMHA_LIMIT_MAX_HEADDIM_TO_256\n#define FMHA_LIMIT_MAX_HEADDIM_TO_256 0\n#endif\n\n#if FMHA_LIMIT_MAX_HEADDIM_TO_256\n\n#define FMHA_FWD_HEADDIM_SWITCH(HEAD_DIM1, HEAD_DIM2, CONST_NAME, ...) \\\n  [&] {                                                                \\\n    if (HEAD_DIM1 <= 32 && HEAD_DIM2 <= 32) {                          \\\n      constexpr ck_tile::index_t CONST_NAME = 32;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 64 && HEAD_DIM2 <= 64) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 64;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 96 && HEAD_DIM2 <= 96) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 96;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 128 && HEAD_DIM2 <= 128) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 128;                     \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 256 && HEAD_DIM2 <= 256) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 256;                     \\\n      __VA_ARGS__();                                                   \\\n    } else {                                                           \\\n      throw std::runtime_error(\"Head-dim sizes not supported!\");       \\\n    }                                                                  \\\n  }()\n\n#else\n\n#define FMHA_FWD_HEADDIM_SWITCH(HEAD_DIM1, HEAD_DIM2, CONST_NAME, ...) \\\n  [&] {                                                                \\\n    if (HEAD_DIM1 <= 32 && HEAD_DIM2 <= 32) {                          \\\n      constexpr ck_tile::index_t CONST_NAME = 32;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 64 && HEAD_DIM2 <= 64) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 64;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 96 && HEAD_DIM2 <= 96) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 96;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 128 && HEAD_DIM2 <= 128) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 128;                     \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 256 && HEAD_DIM2 <= 256) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 256;                     \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 512 && HEAD_DIM2 <= 512) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 512;                     \\\n      __VA_ARGS__();                                                   \\\n    } else {                                                           \\\n      throw std::runtime_error(\"Head-dim sizes not supported!\");       \\\n    }                                                                  \\\n  }()\n\n#endif\n\n#define FMHA_BWD_HEADDIM_SWITCH(HEAD_DIM1, HEAD_DIM2, CONST_NAME, ...) \\\n  [&] {                                                                \\\n    if (HEAD_DIM1 <= 32 && HEAD_DIM2 <= 32) {                          \\\n      constexpr ck_tile::index_t CONST_NAME = 32;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 64 && HEAD_DIM2 <= 64) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 64;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 96 && HEAD_DIM2 <= 96) {                   \\\n      constexpr ck_tile::index_t CONST_NAME = 96;                      \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 128 && HEAD_DIM2 <= 128) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 128;                     \\\n      __VA_ARGS__();                                                   \\\n    } else if (HEAD_DIM1 <= 256 && HEAD_DIM2 <= 256) {                 \\\n      constexpr ck_tile::index_t CONST_NAME = 256;                     \\\n      __VA_ARGS__();                                                   \\\n    } else {                                                           \\\n      throw std::runtime_error(\"Head-dim sizes not supported!\");       \\\n    }                                                                  \\\n  }()\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/ck_tiled_rand_uniform_kernel.h",
    "content": "// SPDX-License-Identifier: MIT\n// Copyright (c) 2018-2024, Advanced Micro Devices, Inc. All rights reserved.\n\n#pragma once\n\n#include <ck_tile/core.hpp>\n#include <ck_tile/ops/fmha.hpp>\n#include <ck_tile/ops/fmha/block/block_dropout.hpp>\n#include <ck_tile/ops/gemm/block/block_gemm_areg_bsmem_creg_v2.hpp>\n#include <ck_tile/ops/gemm/block/block_gemm_problem.hpp>\n\ntemplate <typename RandValOutputDataType, bool kIsGroupMode>\nstruct FmhaRandUniformKernel {\n  using BlockTile = ck_tile::sequence<128, 64, 32>;\n  using WarpTile = ck_tile::sequence<32, 32, 8>;\n  using BlockWarps = ck_tile::sequence<4, 1, 1>;\n\n  using BlockGemmTileShape =\n      ck_tile::TileGemmShape<BlockTile, BlockWarps, WarpTile>;\n\n  static constexpr ck_tile::index_t kBlockSize =\n      BlockGemmTileShape::NumWarps * ck_tile::get_warp_size();\n  static constexpr ck_tile::index_t kBlockPerCu = 1;\n\n  __device__ static constexpr auto GetBlockGemm() {\n    using namespace ck_tile;\n\n    using BlockGemmProblem_ = ck_tile::BlockGemmProblem<\n        ck_tile::fp16_t,\n        ck_tile::fp16_t,\n        float,\n        kBlockSize,\n        BlockGemmTileShape>;\n\n    // using the default policy, which use M32xN32xK8 warp_tile\n    return ck_tile::BlockGemmARegBSmemCRegV2<BlockGemmProblem_>{};\n  };\n\n  using BlockGemm = decltype(GetBlockGemm());\n\n  using MyBlockDropout = ck_tile::BlockDropout;\n\n  static constexpr bool kPadSeqLenQ = true;\n  static constexpr bool kPadSeqLenK = true;\n\n  using BlockGemmShape =\n      ck_tile::remove_cvref_t<typename BlockGemm::BlockGemmShape>;\n  static constexpr ck_tile::index_t kMPerBlock = BlockGemmShape::kM;\n  static constexpr ck_tile::index_t kNPerBlock = BlockGemmShape::kN;\n\n  // kargs use aggregate initializer, so no constructor will provided\n  // use inheritance to minimize karg size\n  // user need to use MakeKargs() function to create kargs.\n  struct FmhaRandUniformCommonKargs {\n    void* rand_val_ptr;\n\n    ck_tile::index_t seqlen_q;\n    ck_tile::index_t seqlen_k;\n\n    ck_tile::index_t num_heads;\n    ck_tile::index_t num_batches;\n\n    ck_tile::index_t stride_seqlen_q;\n    ck_tile::index_t stride_seqlen_k;\n\n    ck_tile::index_t stride_nhead;\n\n    uint64_t seed = 1;\n    uint64_t offset = 0;\n  };\n\n  struct FmhaRandUniformBatchModeKargs : FmhaRandUniformCommonKargs {\n    ck_tile::index_t stride_batch;\n  };\n\n  struct FmhaRandUniformGroupModeKargs : FmhaRandUniformCommonKargs {\n    const int32_t* seqstart_q_ptr;\n    const int32_t* seqstart_k_ptr;\n    const int32_t* seqlen_k_ptr;\n  };\n\n  using Kargs = std::conditional_t<\n      kIsGroupMode,\n      FmhaRandUniformGroupModeKargs,\n      FmhaRandUniformBatchModeKargs>;\n\n  template <bool Cond = !kIsGroupMode>\n  __host__ static constexpr std::enable_if_t<Cond, Kargs> MakeKargs(\n      void* rand_val_ptr,\n      ck_tile::index_t seqlen_q,\n      ck_tile::index_t seqlen_k,\n      ck_tile::index_t num_heads,\n      ck_tile::index_t num_batches,\n      ck_tile::index_t stride_seqlen_q,\n      ck_tile::index_t stride_seqlen_k,\n      ck_tile::index_t stride_nhead,\n      ck_tile::index_t stride_batch,\n      std::tuple<uint64_t, uint64_t> drop_seed_offset) {\n    Kargs kargs{\n        {rand_val_ptr,\n         seqlen_q,\n         seqlen_k,\n         num_heads,\n         num_batches,\n         stride_seqlen_q,\n         stride_seqlen_k,\n         stride_nhead,\n         std::get<0>(drop_seed_offset),\n         std::get<1>(drop_seed_offset)},\n        stride_batch};\n\n    return kargs;\n  }\n\n  template <bool Cond = kIsGroupMode>\n  __host__ static constexpr std::enable_if_t<Cond, Kargs> MakeKargs(\n      void* rand_val_ptr,\n      ck_tile::index_t seqlen_q,\n      ck_tile::index_t seqlen_k,\n      ck_tile::index_t num_heads,\n      ck_tile::index_t num_batches,\n      ck_tile::index_t stride_seqlen_q,\n      ck_tile::index_t stride_seqlen_k,\n      ck_tile::index_t stride_nhead,\n      const void* seqstart_q_ptr,\n      const void* seqstart_k_ptr,\n      const void* seqlen_k_ptr,\n      std::tuple<uint64_t, uint64_t> drop_seed_offset) {\n    Kargs kargs{\n        {rand_val_ptr,\n         seqlen_q,\n         seqlen_k,\n         num_heads,\n         num_batches,\n         stride_seqlen_q,\n         stride_seqlen_k,\n         stride_nhead,\n         std::get<0>(drop_seed_offset),\n         std::get<1>(drop_seed_offset)},\n        reinterpret_cast<const int32_t*>(seqstart_q_ptr),\n        reinterpret_cast<const int32_t*>(seqstart_k_ptr),\n        reinterpret_cast<const int32_t*>(seqlen_k_ptr)};\n\n    return kargs;\n  }\n\n  __host__ static constexpr auto GridSize(\n      ck_tile::index_t batch_size_,\n      ck_tile::index_t nhead_,\n      ck_tile::index_t seqlen_q_,\n      ck_tile::index_t seqlen_k_) {\n    (void)seqlen_k_; // not used at present\n\n    // at present, seqlen_k is not splitted by thread-groups\n    return dim3(\n        ck_tile::integer_divide_ceil(seqlen_q_, kMPerBlock),\n        nhead_,\n        batch_size_);\n  }\n\n  __device__ static constexpr auto GetTileIndex(\n      ck_tile::index_t seqlen_q_,\n      ck_tile::index_t seqlen_k_) {\n    (void)seqlen_q_; // not used at present\n    (void)seqlen_k_; // not used at present\n\n    const ck_tile::index_t i_block = blockIdx.x;\n    const ck_tile::index_t i_nhead = blockIdx.y;\n    const ck_tile::index_t i_batch = blockIdx.z;\n\n    return ck_tile::make_tuple(i_block, i_nhead, i_batch);\n  }\n\n  __host__ static constexpr auto BlockSize() {\n    return dim3(kBlockSize);\n  }\n\n  __device__ static constexpr ck_tile::index_t GetSmemSize() {\n    return MyBlockDropout::MakeRandValLdsBlockDescriptor<BlockGemm>()\n        .get_element_space_size();\n  }\n\n  template <typename RandValDramBlockWindowTmp>\n  __device__ void main_loop(\n      const Kargs& kargs,\n      const ck_tile::philox& ph,\n      void* randval_smem_ptr,\n      RandValDramBlockWindowTmp& randval_dram_block_window_tmp) const {\n    using namespace ck_tile;\n\n    auto randval_dram_window = MyBlockDropout::MakeRandvalDramWindow<BlockGemm>(\n        randval_dram_block_window_tmp, 0);\n\n    const auto num_total_loop =\n        ck_tile::integer_divide_ceil(kargs.seqlen_k, kNPerBlock);\n    index_t i_total_loops = 0;\n\n    do {\n      constexpr auto config = BlockGemm::Policy::template GetWarpGemmMWarpNWarp<\n          typename BlockGemm::Problem>();\n      using WG = remove_cvref_t<decltype(config.template at<0>())>;\n      constexpr index_t MWarp = config.template at<1>();\n      constexpr index_t NWarp = config.template at<2>();\n      constexpr index_t kMPerStep = MWarp * WG::kM;\n      constexpr index_t kNPerStep = NWarp * WG::kN;\n\n      // randval tile in LDS\n      auto randval_lds = make_tensor_view<address_space_enum::lds>(\n          reinterpret_cast<uint8_t*>(randval_smem_ptr),\n          MyBlockDropout::MakeRandValLdsBlockDescriptor<BlockGemm>());\n\n      auto randval_lds_window = make_tile_window(\n          randval_lds,\n          MyBlockDropout::MakeRandValLdsBlockDescriptor<BlockGemm>()\n              .get_lengths(),\n          {0, 0});\n\n      // register distribute\n      auto randval_dist_generated = make_static_distributed_tensor<uint8_t>(\n          MyBlockDropout::MakeRandValTileDistribution<BlockGemm>());\n\n      static_assert(randval_dist_generated.kThreadElementSpaceSize == 16);\n\n      auto randval_lds_read_window = make_tile_window(\n          randval_lds_window.get_bottom_tensor_view(),\n          randval_lds_window.get_window_lengths(),\n          randval_lds_window.get_window_origin(),\n          MyBlockDropout::MakeRandValLdsShuffleTileDistribution<BlockGemm>());\n\n      const int start_m0_idx =\n          randval_dram_window.get_window_origin().at(number<0>{});\n      const int start_n0_idx = i_total_loops * kNPerBlock;\n\n      static_for<0, kMPerBlock / kMPerStep, 1>{}([&](auto i_m0) {\n        static_for<0, kNPerBlock / kNPerStep, 1>{}([&](auto i_n0) {\n          const auto [block_row_start, block_col_start] = [&]() {\n            if constexpr (MWarp > 1) {\n              int block_row_start_ =\n                  (start_m0_idx / WG::kM) + (i_m0 * MWarp) + get_warp_id();\n              int block_col_start_ = start_n0_idx / WG::kN + i_n0;\n              return make_tuple(block_row_start_, block_col_start_);\n            } else {\n              int block_row_start_ = (start_m0_idx / WG::kM) + i_m0;\n              int block_col_start_ =\n                  (start_n0_idx / WG::kN) + (i_n0 * NWarp) + get_warp_id();\n              return make_tuple(block_row_start_, block_col_start_);\n            };\n          }();\n\n          uint2 rowcol = make_uint2(block_row_start, block_col_start);\n\n          // generate random number\n          uint8_t random_uint8_t[16];\n          ph.get_random_16x8(\n              random_uint8_t, reinterpret_cast<unsigned long long&>(rowcol));\n\n          constexpr auto randval_dist_generated_spans =\n              decltype(randval_dist_generated)::get_distributed_spans();\n          int i_random_idx = 0;\n          sweep_tile_span(\n              randval_dist_generated_spans[number<0>{}], [&](auto idx0) {\n                sweep_tile_span(\n                    randval_dist_generated_spans[number<1>{}], [&](auto idx1) {\n                      constexpr auto i_j_idx = make_tuple(idx0, idx1);\n                      randval_dist_generated(i_j_idx) =\n                          random_uint8_t[i_random_idx++];\n                    });\n              });\n          // save to LDS\n          store_tile(randval_lds_window, randval_dist_generated);\n          block_sync_lds();\n          // read from LDS to register\n          auto randval = load_tile(randval_lds_read_window);\n          // save to Global\n          const auto randval_store = cast_tile<RandValOutputDataType>(randval);\n          store_tile(randval_dram_window, randval_store);\n          move_tile_window(randval_dram_window, {0, kNPerStep});\n        });\n        move_tile_window(randval_dram_window, {kMPerStep, -kNPerBlock});\n      });\n\n      move_tile_window(randval_dram_window, {-kMPerBlock, kNPerBlock});\n\n    } while (++i_total_loops < num_total_loop);\n  }\n\n  __device__ void operator()(Kargs kargs) const {\n    using namespace ck_tile;\n\n    // allocate LDS\n    __shared__ char smem_ptr[GetSmemSize()];\n\n    // divide problem\n    const auto [i_tile_m, i_nhead, i_batch] =\n        GetTileIndex(kargs.seqlen_q, kargs.seqlen_k);\n\n    const index_t i_m0 = __builtin_amdgcn_readfirstlane(i_tile_m * kMPerBlock);\n\n    long_index_t batch_offset_randval = 0;\n\n    if constexpr (kIsGroupMode) {\n      // get starting offset for each batch\n      const long_index_t query_start = kargs.seqstart_q_ptr[i_batch];\n\n      batch_offset_randval = query_start * kargs.stride_seqlen_q;\n\n      // get real # queries & # keys under group mode\n      const auto adjusted_seqstart_q_ptr = kargs.seqstart_q_ptr + i_batch;\n      kargs.seqlen_q = adjusted_seqstart_q_ptr[1] - adjusted_seqstart_q_ptr[0];\n\n      if (kargs.seqlen_q <= i_m0) {\n        return;\n      }\n\n      if (kargs.seqlen_k_ptr != nullptr) {\n        kargs.seqlen_k = kargs.seqlen_k_ptr[i_batch];\n      } else {\n        const auto adjusted_seqstart_k_ptr = kargs.seqstart_k_ptr + i_batch;\n        kargs.seqlen_k =\n            adjusted_seqstart_k_ptr[1] - adjusted_seqstart_k_ptr[0];\n      }\n    } else {\n      batch_offset_randval =\n          static_cast<long_index_t>(i_batch) * kargs.stride_batch;\n    }\n\n    constexpr auto randval_dram_window_lengths =\n        make_tuple(number<kMPerBlock>{}, number<kNPerBlock>{});\n\n    RandValOutputDataType* rand_val_ptr =\n        reinterpret_cast<RandValOutputDataType*>(kargs.rand_val_ptr) +\n        static_cast<long_index_t>(i_nhead) * kargs.stride_nhead +\n        batch_offset_randval;\n\n    const auto randval_dram = [&]() {\n      const auto randval_dram_naive =\n          make_naive_tensor_view<address_space_enum::global>(\n              rand_val_ptr,\n              make_tuple(kargs.seqlen_q, kargs.seqlen_k),\n              make_tuple(kargs.stride_seqlen_q, kargs.stride_seqlen_k),\n              number<1>{},\n              number<1>{});\n\n      return pad_tensor_view(\n          randval_dram_naive,\n          randval_dram_window_lengths,\n          ck_tile::sequence<kPadSeqLenQ, kPadSeqLenK>{});\n    }();\n\n    auto randval_dram_block_window_tmp =\n        make_tile_window(randval_dram, randval_dram_window_lengths, {i_m0, 0});\n\n    ck_tile::philox ph(\n        kargs.seed,\n        kargs.offset + (i_batch * kargs.num_heads + i_nhead) * get_warp_size() +\n            get_lane_id());\n\n    main_loop(kargs, ph, smem_ptr, randval_dram_block_window_tmp);\n  }\n};\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/generate_instances.py",
    "content": "# noqa: C801\n# Copyright (c) 2023-2024, Advanced Micro Devices, Inc. All rights reserved.\n#\n# This source code is licensed under the BSD-style license found in the\n# LICENSE file in the root directory of this source tree.\n#\n\nimport os\nfrom pathlib import Path\nfrom typing import List\n\nFMHA_COPYRIGHT_HEADER = \"\"\"\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `{file}`\n */\n\"\"\".format(\n    file=os.path.relpath(os.path.realpath(__file__), start=Path(__file__).parents[4])\n)\n\nFMHA_INFER_INSTANCE_TEMPLATE_INC = \"\"\"\n#include <ck_tile/core/numeric/{dtype_file}.hpp>\n#include \\\"ck_tiled_fmha_{mode}_infer.h\\\"\n\"\"\"\n\nFMHA_INFER_INSTANCE_TEMPLATE = \"\"\"\n{extern}template void run_{mode}_infer_mask_bias_dropout_dispatch<\n    {dtype},\n    {has_mask},\n    {has_bias},\n    {has_dropout},\n    {max_k}>({cap_mode}ForwardParams& param, hipStream_t stream);\n\"\"\"\n\nFMHA_INFER_INSTANCE_FNAME = (\n    \"fmha_{mode}_infer_{dtype_str}_{has_or_no_mask_str}_\"\n    \"{has_or_no_bias_str}_{has_or_no_dropout_str}_{max_k_str}.cpp\"\n)\n\nFMHA_FORWARD_INSTANCE_TEMPLATE_INC = \"\"\"\n#include <ck_tile/core/numeric/{dtype_file}.hpp>\n#include \\\"ck_tiled_fmha_{mode}_forward.h\\\"\n\"\"\"\n\nFMHA_FORWARD_INSTANCE_TEMPLATE = \"\"\"\n{extern}template void run_{mode}_forward_mask_bias_dropout_dispatch<\n    {dtype},\n    {has_mask},\n    {has_bias},\n    {has_dropout},\n    {max_k}>({cap_mode}ForwardParams& param, hipStream_t stream);\n\"\"\"\n\nFMHA_FORWARD_INSTANCE_FNAME = (\n    \"fmha_{mode}_forward_{dtype_str}_{has_or_no_mask_str}_\"\n    \"{has_or_no_bias_str}_{has_or_no_dropout_str}_{max_k_str}.cpp\"\n)\n\nFMHA_BACKWARD_INSTANCE_TEMPLATE_INC = \"\"\"\n#include <ck_tile/core/numeric/{dtype_file}.hpp>\n#include \\\"ck_tiled_fmha_{mode}_backward.h\\\"\n\"\"\"\n\nFMHA_BACKWARD_INSTANCE_TEMPLATE = \"\"\"\n{extern}template void run_{mode}_backward_mask_bias_dropout_dispatch<\n    {dtype},\n    {has_mask},\n    {has_bias},\n    {has_bias_grad},\n    {has_dropout},\n    {max_k}>({cap_mode}BackwardParams& param, hipStream_t stream);\n\"\"\"\n\nFMHA_BACKWARD_INSTANCE_FNAME = (\n    \"fmha_{mode}_backward_{dtype_str}_{has_or_no_mask_str}_\"\n    \"{has_or_no_bias_str}_{has_or_no_biasgrad_str}_{has_or_no_dropout_str}_{max_k_str}.cpp\"\n)\n\nFMHA_INSTANCE_REF_FNAME = \"fmha_{mode}_{function}_{dtype}_instances_ref.h\"\n\nBOOL_MAP = {True: \"true\", False: \"false\"}\n\nBOOL_MAP_MASK = {\n    True: \"has_mask\",\n    False: \"no_mask\",\n}\n\nBOOL_MAP_BIAS = {\n    True: \"has_bias\",\n    False: \"no_bias\",\n}\n\nBOOL_MAP_BIASGRAD = {\n    True: \"has_biasgrad\",\n    False: \"no_biasgrad\",\n}\n\nBOOL_MAP_DROPOUT = {\n    True: \"has_dropout\",\n    False: \"no_dropout\",\n}\n\nINT_MAP_MAX_K = {hd: f\"maxk_{hd}\" for hd in [32, 64, 96, 128, 256, 512]}\n\nTYPE_CTYPE_MAP = {\n    \"fp16\": \"ck_tile::fp16_t\",\n    \"bf16\": \"ck_tile::bf16_t\",\n}\n\nTYPE_FNAME_MAP = {\n    \"fp16\": \"half\",\n    \"bf16\": \"bfloat16\",\n}\n\nMODE_NAME_MAP = {\n    \"batched\": \"Batched\",\n    \"grouped\": \"Grouped\",\n}\n\n\ndef create_infer_instances(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            for has_mask in [True, False]:\n                for has_bias in [True, False]:\n                    for has_dropout in [True, False]:\n                        for max_k in headdims:\n                            fname = FMHA_INFER_INSTANCE_FNAME.format(\n                                mode=mode,\n                                dtype_str=dtype,\n                                has_or_no_mask_str=BOOL_MAP_MASK[has_mask],\n                                has_or_no_bias_str=BOOL_MAP_BIAS[has_bias],\n                                has_or_no_dropout_str=BOOL_MAP_DROPOUT[has_dropout],\n                                max_k_str=INT_MAP_MAX_K[max_k],\n                            )\n                            infer_instance_inc = (\n                                FMHA_INFER_INSTANCE_TEMPLATE_INC.format(\n                                    mode=mode,\n                                    dtype_file=TYPE_FNAME_MAP[dtype],\n                                )\n                            )\n                            infer_instance = FMHA_INFER_INSTANCE_TEMPLATE.format(\n                                extern=\"\",\n                                mode=mode,\n                                dtype=TYPE_CTYPE_MAP[dtype],\n                                has_mask=BOOL_MAP[has_mask],\n                                has_bias=BOOL_MAP[has_bias],\n                                has_dropout=BOOL_MAP[has_dropout],\n                                max_k=max_k,\n                                cap_mode=MODE_NAME_MAP[mode],\n                            )\n                            (instance_dir / fname).write_text(\n                                FMHA_COPYRIGHT_HEADER\n                                + infer_instance_inc\n                                + infer_instance\n                            )\n\n\ndef create_infer_instances_ref(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            ref_fname = FMHA_INSTANCE_REF_FNAME.format(\n                mode=mode,\n                function=\"infer\",\n                dtype=dtype,\n            )\n            ref_fname_path = instance_dir / ref_fname\n            infer_instance_inc = FMHA_INFER_INSTANCE_TEMPLATE_INC.format(\n                mode=mode,\n                dtype_file=TYPE_FNAME_MAP[dtype],\n            )\n            with open(ref_fname_path, \"a\") as file:\n                file.write(FMHA_COPYRIGHT_HEADER)\n                file.write(infer_instance_inc)\n                for max_k in headdims:\n                    for has_bias in [True, False]:\n                        for has_dropout in [True, False]:\n                            for has_mask in [True, False]:\n                                infer_instance = FMHA_INFER_INSTANCE_TEMPLATE.format(\n                                    extern=\"extern \",\n                                    mode=mode,\n                                    dtype=TYPE_CTYPE_MAP[dtype],\n                                    has_mask=BOOL_MAP[has_mask],\n                                    has_bias=BOOL_MAP[has_bias],\n                                    has_dropout=BOOL_MAP[has_dropout],\n                                    max_k=max_k,\n                                    cap_mode=MODE_NAME_MAP[mode],\n                                )\n                                file.write(infer_instance)\n\n\ndef create_forward_instances(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            for has_mask in [True, False]:\n                for has_bias in [True, False]:\n                    for has_dropout in [True, False]:\n                        for max_k in headdims:\n                            fname = FMHA_FORWARD_INSTANCE_FNAME.format(\n                                mode=mode,\n                                dtype_str=dtype,\n                                has_or_no_mask_str=BOOL_MAP_MASK[has_mask],\n                                has_or_no_bias_str=BOOL_MAP_BIAS[has_bias],\n                                has_or_no_dropout_str=BOOL_MAP_DROPOUT[has_dropout],\n                                max_k_str=INT_MAP_MAX_K[max_k],\n                            )\n                            forward_instance_inc = (\n                                FMHA_FORWARD_INSTANCE_TEMPLATE_INC.format(\n                                    mode=mode,\n                                    dtype_file=TYPE_FNAME_MAP[dtype],\n                                )\n                            )\n                            forward_instance = FMHA_FORWARD_INSTANCE_TEMPLATE.format(\n                                extern=\"\",\n                                mode=mode,\n                                dtype=TYPE_CTYPE_MAP[dtype],\n                                has_mask=BOOL_MAP[has_mask],\n                                has_bias=BOOL_MAP[has_bias],\n                                has_dropout=BOOL_MAP[has_dropout],\n                                max_k=max_k,\n                                cap_mode=MODE_NAME_MAP[mode],\n                            )\n                            (instance_dir / fname).write_text(\n                                FMHA_COPYRIGHT_HEADER\n                                + forward_instance_inc\n                                + forward_instance\n                            )\n\n\ndef create_forward_instances_ref(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            ref_fname = FMHA_INSTANCE_REF_FNAME.format(\n                mode=mode,\n                function=\"forward\",\n                dtype=dtype,\n            )\n            ref_fname_path = instance_dir / ref_fname\n            forward_instance_inc = FMHA_FORWARD_INSTANCE_TEMPLATE_INC.format(\n                mode=mode,\n                dtype_file=TYPE_FNAME_MAP[dtype],\n            )\n            with open(ref_fname_path, \"a\") as file:\n                file.write(FMHA_COPYRIGHT_HEADER)\n                file.write(forward_instance_inc)\n                for max_k in headdims:\n                    for has_bias in [True, False]:\n                        for has_dropout in [True, False]:\n                            for has_mask in [True, False]:\n                                forward_instance = (\n                                    FMHA_FORWARD_INSTANCE_TEMPLATE.format(\n                                        extern=\"extern \",\n                                        mode=mode,\n                                        dtype=TYPE_CTYPE_MAP[dtype],\n                                        has_mask=BOOL_MAP[has_mask],\n                                        has_bias=BOOL_MAP[has_bias],\n                                        has_dropout=BOOL_MAP[has_dropout],\n                                        max_k=max_k,\n                                        cap_mode=MODE_NAME_MAP[mode],\n                                    )\n                                )\n                                file.write(forward_instance)\n\n\ndef create_backward_instances(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            for has_mask in [True, False]:\n                for has_bias, has_bias_grad in [\n                    [True, False],\n                    [True, True],\n                    [False, False],\n                ]:\n                    for has_dropout in [True, False]:\n                        for max_k in headdims:\n                            fname = FMHA_BACKWARD_INSTANCE_FNAME.format(\n                                mode=mode,\n                                dtype_str=dtype,\n                                has_or_no_mask_str=BOOL_MAP_MASK[has_mask],\n                                has_or_no_bias_str=BOOL_MAP_BIAS[has_bias],\n                                has_or_no_biasgrad_str=BOOL_MAP_BIASGRAD[has_bias_grad],\n                                has_or_no_dropout_str=BOOL_MAP_DROPOUT[has_dropout],\n                                max_k_str=INT_MAP_MAX_K[max_k],\n                            )\n                            backward_instance_inc = (\n                                FMHA_BACKWARD_INSTANCE_TEMPLATE_INC.format(\n                                    mode=mode,\n                                    dtype_file=TYPE_FNAME_MAP[dtype],\n                                )\n                            )\n                            backward_instance = FMHA_BACKWARD_INSTANCE_TEMPLATE.format(\n                                extern=\"\",\n                                mode=mode,\n                                dtype=TYPE_CTYPE_MAP[dtype],\n                                has_mask=BOOL_MAP[has_mask],\n                                has_bias=BOOL_MAP[has_bias],\n                                has_bias_grad=BOOL_MAP[has_bias_grad],\n                                has_dropout=BOOL_MAP[has_dropout],\n                                max_k=max_k,\n                                cap_mode=MODE_NAME_MAP[mode],\n                            )\n                            (instance_dir / fname).write_text(\n                                FMHA_COPYRIGHT_HEADER\n                                + backward_instance_inc\n                                + backward_instance\n                            )\n\n\ndef create_backward_instances_ref(instance_dir: Path, headdims: List) -> None:\n    for mode in [\"batched\", \"grouped\"]:\n        for dtype in [\"fp16\", \"bf16\"]:\n            ref_fname = FMHA_INSTANCE_REF_FNAME.format(\n                mode=mode,\n                function=\"backward\",\n                dtype=dtype,\n            )\n            ref_fname_path = instance_dir / ref_fname\n            backward_instance_inc = FMHA_BACKWARD_INSTANCE_TEMPLATE_INC.format(\n                mode=mode,\n                dtype_file=TYPE_FNAME_MAP[dtype],\n            )\n            with open(ref_fname_path, \"a\") as file:\n                file.write(FMHA_COPYRIGHT_HEADER)\n                file.write(backward_instance_inc)\n                for max_k in headdims:\n                    for has_bias, has_bias_grad in [\n                        [True, False],\n                        [True, True],\n                        [False, False],\n                    ]:\n                        for has_dropout in [True, False]:\n                            for has_mask in [True, False]:\n                                backward_instance = (\n                                    FMHA_BACKWARD_INSTANCE_TEMPLATE.format(\n                                        extern=\"extern \",\n                                        mode=mode,\n                                        dtype=TYPE_CTYPE_MAP[dtype],\n                                        has_mask=BOOL_MAP[has_mask],\n                                        has_bias=BOOL_MAP[has_bias],\n                                        has_bias_grad=BOOL_MAP[has_bias_grad],\n                                        has_dropout=BOOL_MAP[has_dropout],\n                                        max_k=max_k,\n                                        cap_mode=MODE_NAME_MAP[mode],\n                                    )\n                                )\n                                file.write(backward_instance)\n\n\nif __name__ == \"__main__\":\n    headdims_fwd = [32, 64, 96, 128, 256, 512]\n    headdims_bwd = [32, 64, 96, 128, 256]\n\n    this_dir = os.path.dirname(__file__)\n    output_dir = Path(this_dir) / \"instances\"\n    output_dir.mkdir(parents=True, exist_ok=True)\n\n    # remove existing files in the directory\n    files = os.listdir(output_dir)\n    for ff in files:\n        file_path = os.path.join(output_dir, ff)\n        os.remove(file_path)\n\n    create_infer_instances(output_dir, headdims_fwd)\n    create_infer_instances_ref(output_dir, headdims_fwd)\n    create_forward_instances(output_dir, headdims_fwd)\n    create_forward_instances_ref(output_dir, headdims_fwd)\n    create_backward_instances(output_dir, headdims_bwd)\n    create_backward_instances_ref(output_dir, headdims_bwd)\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n\nextern template void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_backward.h\"\n\ntemplate void run_batched_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(BatchedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_bf16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_forward_fp16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_forward.h\"\n\ntemplate void run_batched_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_bf16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n\nextern template void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_batched_infer_fp16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_batched_infer.h\"\n\ntemplate void run_batched_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(BatchedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_bf16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_has_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_has_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_has_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    true,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    128>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    256>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    32>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    64>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_backward_fp16_no_mask_no_bias_no_biasgrad_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_backward.h\"\n\ntemplate void run_grouped_backward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    false,\n    96>(GroupedBackwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_bf16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_forward_fp16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_forward.h\"\n\ntemplate void run_grouped_forward_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_bf16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/bfloat16.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::bf16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_has_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_instances_ref.h",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    true,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n\nextern template void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_has_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    true,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_has_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    true,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_128.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    128>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_256.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    256>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_32.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    32>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_512.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    512>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_64.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    64>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/attention/hip_fmha/instances/fmha_grouped_infer_fp16_no_mask_no_bias_no_dropout_maxk_96.cpp",
    "content": "\n/*\n  Copyright (c) 2024, Advanced Micro Devices, Inc. All rights reserved.\n *\n * This source code is licensed under the BSD-style license found in the\n * LICENSE file in the root directory of this source tree.\n *\n * The file is automatically generated, don't modify!\n * See the generator script\n * `xformers/csrc/attention/hip_fmha/generate_instances.py`\n */\n\n#include <ck_tile/core/numeric/half.hpp>\n#include \"ck_tiled_fmha_grouped_infer.h\"\n\ntemplate void run_grouped_infer_mask_bias_dropout_dispatch<\n    ck_tile::fp16_t,\n    false,\n    false,\n    false,\n    96>(GroupedForwardParams& param, hipStream_t stream);\n"
  },
  {
    "path": "xformers/csrc/nvcc_info.cu",
    "content": "#include <torch/csrc/stable/library.h>\n\n#include \"pt_stable_utils.h\"\n\nnamespace {\nstd::tuple<int64_t, int64_t, int64_t> nvcc_build_version() {\n  return std::make_tuple(\n      __CUDACC_VER_MAJOR__, __CUDACC_VER_MINOR__, __CUDACC_VER_BUILD__);\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_FRAGMENT(xformers, m) {\n  m.def(\"_nvcc_build_version() -> (int, int, int)\");\n  m.impl(\"_nvcc_build_version\", TORCH_BOX(nvcc_build_version));\n}\n"
  },
  {
    "path": "xformers/csrc/pt_stable_utils.cu",
    "content": "#include <deque>\n#include <mutex>\n#include <vector>\n\n#include \"pt_stable_utils.h\"\n\nnamespace {\n\nstd::deque<std::once_flag> device_flags;\nstd::vector<cudaDeviceProp> device_properties;\n\nvoid initCUDAContextVectors() {\n  static bool init_flag [[maybe_unused]] = []() {\n    int num_gpus;\n    STD_CUDA_CHECK(cudaGetDeviceCount(&num_gpus));\n    device_flags.resize(num_gpus);\n    device_properties.resize(num_gpus);\n    return true;\n  }();\n}\n\nvoid initDeviceProperty(torch::stable::accelerator::DeviceIndex device_index) {\n  cudaDeviceProp device_prop{};\n  STD_CUDA_CHECK(cudaGetDeviceProperties(&device_prop, device_index));\n  device_properties[device_index] = device_prop;\n}\n\n} // namespace\n\ncudaDeviceProp* xf_getCurrentDeviceProperties() {\n  initCUDAContextVectors();\n  torch::stable::accelerator::DeviceIndex device =\n      torch::stable::accelerator::getCurrentDeviceIndex();\n  std::call_once(device_flags[device], initDeviceProperty, device);\n  return &device_properties[device];\n}\n"
  },
  {
    "path": "xformers/csrc/pt_stable_utils.h",
    "content": "#pragma once\n\n#include <array>\n#include <cassert>\n#include <cmath>\n#include <limits>\n#include <optional>\n#include <tuple>\n#include <type_traits>\n#include <vector>\n\n#ifdef USE_CUDA\n#include <cuda.h>\n#include <cuda_runtime.h>\n#endif\n\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/stableivalue_conversions.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/TensorAccessor.h>\n#include <torch/headeronly/util/Metaprogramming.h>\n\n#ifdef USE_CUDA\n\n#define XF_CUDA_DRIVER_CHECK(EXPR)                   \\\n  do {                                               \\\n    const CUresult __err = EXPR;                     \\\n    if (__err != CUDA_SUCCESS) {                     \\\n      throw std::runtime_error(\"CUDA driver error\"); \\\n    }                                                \\\n  } while (0)\n\ncudaDeviceProp* xf_getCurrentDeviceProperties();\n\n#endif\n\nnamespace {\n\n#ifdef USE_CUDA\n\ncudaStream_t xf_getCurrentCUDAStream(\n    torch::stable::accelerator::DeviceIndex index = -1) {\n  // This would be the correct code to use, but it's currently broken.\n  // return reinterpret_cast<cudaStream_t>(\n  //     torch::stable::accelerator::getCurrentStream(\n  //         torch::stable::accelerator::getCurrentDeviceIndex())\n  //         .id());\n  void* ret;\n  TORCH_ERROR_CODE_CHECK(aoti_torch_get_current_cuda_stream(index, &ret));\n  return static_cast<cudaStream_t>(ret);\n}\n\ntemplate <typename T>\nconstexpr __host__ __device__ inline T ceil_div(T a, T b) {\n  return (a + b - 1) / b;\n}\n\n#endif\n\ntemplate <typename dtype, size_t ndim>\nauto xf_packed_accessor(const torch::stable::Tensor& t) {\n  return torch::headeronly::HeaderOnlyGenericPackedTensorAccessor<dtype, ndim>(\n      t.mutable_data_ptr<dtype>(), t.sizes().data(), t.strides().data());\n}\n\ninline int32_t xf_get_layout(const torch::stable::Tensor& self) {\n  int32_t layout;\n  TORCH_ERROR_CODE_CHECK(aoti_torch_get_layout(self.get(), &layout));\n  return layout;\n}\n\ninline bool xf_is_sparse(const torch::stable::Tensor& self) {\n  return xf_get_layout(self) != aoti_torch_layout_strided();\n}\n\ninline torch::stable::Tensor xf_view_dtype(\n    const torch::stable::Tensor& self,\n    torch::headeronly::ScalarType dtype) {\n  const auto num_args = 2;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self), torch::stable::detail::from(dtype)};\n  // view.dtype(Tensor(a) self, ScalarType dtype) -> Tensor(a)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::view\", \"dtype\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_slice(\n    const torch::stable::Tensor& self,\n    int64_t dim,\n    std::optional<int64_t> start,\n    std::optional<int64_t> end) {\n  const auto num_args = 5;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self),\n      torch::stable::detail::from(dim),\n      torch::stable::detail::from(start),\n      torch::stable::detail::from(end),\n      torch::stable::detail::from(1)};\n  // slice.Tensor(Tensor(a) self, int dim=0, SymInt? start=None, SymInt?\n  // end=None, SymInt step=1) -> Tensor(a)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::slice\", \"Tensor\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_select(\n    const torch::stable::Tensor& self,\n    int64_t dim,\n    int64_t index) {\n  const auto num_args = 3;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self),\n      torch::stable::detail::from(dim),\n      torch::stable::detail::from(index)};\n  // select.int(Tensor(a) self, int dim, SymInt index) -> Tensor(a)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::select\", \"int\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_permute(\n    const torch::stable::Tensor& self,\n    std::vector<int64_t> dims) {\n  const auto num_args = 2;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self), torch::stable::detail::from(dims)};\n  // permute(Tensor(a) self, int[] dims) -> Tensor(a)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::permute\", \"\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_contiguous(\n    const torch::stable::Tensor& self,\n    int32_t memory_format = aoti_torch_memory_format_contiguous_format()) {\n  const auto num_args = 2;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self),\n      torch::stable::detail::from(memory_format),\n  };\n  // contiguous(Tensor(a) self, *, MemoryFormat memory_format=contiguous_format)\n  // -> Tensor(a)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::contiguous\", \"\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_zeros(\n    std::vector<int64_t> size,\n    std::optional<torch::headeronly::ScalarType> dtype = std::nullopt,\n    std::optional<torch::stable::Device> device = std::nullopt,\n    std::optional<bool> pin_memory = std::nullopt) {\n  const auto num_args = 5;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(size),\n      torch::stable::detail::from(dtype),\n      torch::stable::detail::from(std::nullopt),\n      torch::stable::detail::from(device),\n      torch::stable::detail::from(pin_memory)};\n  // zeros(SymInt[] size, *, ScalarType? dtype=None, Layout? layout=None,\n  // Device? device=None, bool? pin_memory=None) -> Tensor\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::zeros\", \"\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_new_full(\n    const torch::stable::Tensor& self,\n    std::vector<int64_t> size,\n    int64_t fill_value,\n    std::optional<torch::headeronly::ScalarType> dtype = std::nullopt) {\n  // Don't directly dispatch to aten::new_full, because StableIValue doesn't\n  // yet support schemas with Scalar arguments.\n  torch::stable::Tensor ret = torch::stable::new_empty(self, size, dtype);\n  assert(abs(fill_value) < (1ll << (std::numeric_limits<double>::digits + 1)));\n  ret = torch::stable::fill_(ret, fill_value);\n  return ret;\n}\n\ninline torch::stable::Tensor xf_cumsum(\n    const torch::stable::Tensor& self,\n    int dim,\n    std::optional<torch::headeronly::ScalarType> dtype = std::nullopt) {\n  const auto num_args = 3;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self),\n      torch::stable::detail::from(dim),\n      torch::stable::detail::from(dtype)};\n  // cumsum(Tensor self, int dim, *, ScalarType? dtype=None) -> Tensor\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::cumsum\", \"\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ninline torch::stable::Tensor xf_resize_(\n    const torch::stable::Tensor& self,\n    std::vector<int64_t> size,\n    int32_t memory_format = aoti_torch_memory_format_contiguous_format()) {\n  const auto num_args = 3;\n  std::array<StableIValue, num_args> stack{\n      torch::stable::detail::from(self),\n      torch::stable::detail::from(size),\n      torch::stable::detail::from(memory_format)};\n  // resize_(Tensor(a!) self, SymInt[] size, *, MemoryFormat?\n  // memory_format=None) -> Tensor(a!)\n  TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n      \"aten::resize_\", \"\", stack.data(), TORCH_ABI_VERSION));\n  return torch::stable::detail::to<torch::stable::Tensor>(stack[0]);\n}\n\ntemplate <typename T>\ninline T xf_item(const torch::stable::Tensor& self) {\n  // const auto num_args = 1;\n  // std::array<StableIValue, num_args> stack{\n  //     torch::stable::detail::from(self)};\n  // // item(Tensor self) -> Scalar\n  // TORCH_ERROR_CODE_CHECK(torch_call_dispatcher(\n  //     \"aten::item\", \"\", stack.data(), TORCH_ABI_VERSION));\n  // return torch::stable::detail::to<T>(stack[0]);\n  torch::stable::Tensor cpu_self = torch::stable::empty(\n      self.sizes(),\n      self.scalar_type(),\n      std::nullopt,\n      torch::stable::Device(torch::headeronly::kCPU));\n  torch::stable::copy_(cpu_self, self, /*non_blocking=*/false);\n  static_assert(std::is_trivially_copyable_v<T>, \"\");\n  T res = *cpu_self.const_data_ptr<T>();\n  return res;\n}\n\nsize_t xf_element_size(const torch::stable::Tensor& self) {\n#define RETURN_SIZEOF_IF_MATCHES_(cpp_type, dtype)                  \\\n  if (self.scalar_type() == torch::headeronly::ScalarType::dtype) { \\\n    return sizeof(cpp_type);                                        \\\n  }\n  AT_FORALL_SCALAR_TYPES_WITH_COMPLEX_AND_QINTS(RETURN_SIZEOF_IF_MATCHES_)\n#undef RETURN_SIZEOF_IF_MATCHES_\n  throw std::runtime_error(\"Unsupported dtype\");\n}\n\n} // namespace\n"
  },
  {
    "path": "xformers/csrc/sparse24/compute_sparse_tile.h",
    "content": "#pragma once\n\n#include <cutlass/bfloat16.h>\n#include <cutlass/half.h>\n\n#include \"sparse24_pack.h\"\n#include \"static_sort.h\"\n\n// Given 4x4 values, computes the selected indices that will remain after 2:4\n// sparsification, as a bitmask.\n// NOTE: Algorithms might select LESS than 8 values in total in some cases.\n\nnamespace xformers {\nnamespace sp24 {\n\n// Operations that we can apply to rank the values\nstruct IdentityOp {\n  template <typename T>\n  static T CUTLASS_HOST_DEVICE to_ordered(T const& x) {\n    return x;\n  }\n};\n// Can be applied to rank based on absolute value\nstruct AbsOp {\n  template <typename T>\n  static uint16_t CUTLASS_HOST_DEVICE to_ordered(T const& x) {\n    return cutlass::abs(x).storage;\n  }\n};\n\ntemplate <typename Element, typename Pointwise>\nstruct TileValueOrderedT {\n  using ElementCmp = decltype(Pointwise::to_ordered(Element(0)));\n  union {\n    struct {\n      ElementCmp cmp;\n      Element value;\n      uint2b_t col;\n      uint2b_t row;\n    } parts;\n    uint32_t raw;\n  };\n  CUTLASS_DEVICE bool operator<(\n      TileValueOrderedT<Element, Pointwise> const& other) const {\n    return parts.cmp < other.parts.cmp;\n  }\n  CUTLASS_DEVICE TileValueOrderedT() {}\n  CUTLASS_DEVICE TileValueOrderedT(Element value, int col, int row = 0) {\n    parts.value = value;\n    parts.row = uint2b_t{row};\n    parts.col = uint2b_t{col};\n    parts.cmp = Pointwise::to_ordered(value);\n  }\n};\n\n// Given 4x4 values, computes the selected indices that will remain after 2:4\n// sparsification, as a bitmask. We have 2 constraints:\n// (1) At most 2 values per line\n// (2) At most 2 values per column\n// This means we can select at most 8 values in total.\n// ALGO: We use a greedy algorithm, where we take values in the 4x4\n// tile in descending order. If a value fits (because the line/col is not\n// already full), we select it. Then we move on to the next one.\n// NOTE: This algorithm might select LESS than 8 values in total in some cases.\n// NOTE (2): RF are not indexable, so we shouldn't rely on indexing\n//   values at any point, otherwise they will be stored in local memory.\ntemplate <typename Op = IdentityOp>\nstruct LargestValuesGreedy {\n  template <typename T>\n  static CUTLASS_DEVICE T outOfBoundsFillValue() {\n    return -cutlass::platform::numeric_limits<T>::infinity();\n  }\n\n  template <typename Tile4x4Accessor>\n  CUTLASS_DEVICE Indices4x4 operator()(Tile4x4Accessor values) {\n    using TileValueOrdered =\n        TileValueOrderedT<typename Tile4x4Accessor::Element, Op>;\n    using TileValuesFragment = cutlass::Array<TileValueOrdered, 4 * 4>;\n    Indices4x4 indices;\n    TileValuesFragment values_ordered;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 4; ++i) {\n      CUTLASS_PRAGMA_UNROLL\n      for (int j = 0; j < 4; ++j) {\n        values_ordered[i * 4 + j] =\n            TileValueOrdered(values.at(i, j).get(), j, i);\n      }\n    }\n    // Use a sorting network (aka without branches) to avoid\n    // warp divergence\n    StaticSort<TileValuesFragment::kElements> sorter;\n    sorter(values_ordered);\n\n    // bitmask to store how many we have selected on a given row/col\n    // 0 selected: (numPerRow >> 2*row) = 00 (0)\n    // 1 selected: (numPerRow >> 2*row) = 01 (1)\n    // 2 selected: (numPerRow >> 2*row) = 11 (3)\n    uint32_t numPerRow = 0;\n    uint32_t numPerCol = 0;\n    indices = 0;\n\n    // Take as many as we can, starting with the largest values\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = values_ordered.size() - 1; i >= 0; i--) {\n      auto& e = values_ordered[i];\n\n      uint32_t rcount = uint2b_t(numPerRow >> 2 * e.parts.row);\n      uint32_t ccount = uint2b_t(numPerCol >> 2 * e.parts.col);\n      // NOTE: This is more efficient (yet equivalent) to:\n      // `rcount != 3 && ccount != 3`\n      bool selected = (rcount + ccount) <= 2;\n      indices |= selected << (e.parts.col + 4 * e.parts.row);\n\n      numPerRow |= (rcount + selected) << 2 * e.parts.row;\n      numPerCol |= (ccount + selected) << 2 * e.parts.col;\n    }\n    return indices;\n  }\n};\n\n// We consider each rows independantly in order\n// This is to ensure that a row's sparsity pattern is only determined\n// by its values and the rows before (but never the rows after)\n// This enforces causality strictly\ntemplate <typename Op, int k1, int k2, int k3, int k4, bool kBothWays>\nstruct LineByLine {\n  template <typename T>\n  static CUTLASS_DEVICE T outOfBoundsFillValue() {\n    return -cutlass::platform::numeric_limits<T>::infinity();\n  }\n\n  template <typename Tile4x4Accessor>\n  CUTLASS_DEVICE Indices4x4 operator()(Tile4x4Accessor values) {\n    static constexpr int kMaxValuesPerRow[4] = {k1, k2, k3, k4};\n    using TileValueOrdered =\n        TileValueOrderedT<typename Tile4x4Accessor::Element, Op>;\n    using TileValuesFragment = cutlass::Array<TileValueOrdered, 4>;\n    Indices4x4 indices = 0;\n\n    uint32_t numPerCol = 0; // <- see doc in `LargestValuesGreedy`\n\n    CUTLASS_PRAGMA_UNROLL\n    for (int row = 0; row < 4; ++row) {\n      int row_count = 0;\n      TileValuesFragment values_ordered;\n      CUTLASS_PRAGMA_UNROLL\n      for (int col = 0; col < 4; ++col) {\n        values_ordered[col] = TileValueOrdered(values.at(row, col).get(), col);\n      }\n      // Use a sorting network (aka without branches) to avoid\n      // warp divergence\n      StaticSort<TileValuesFragment::kElements> sorter;\n      sorter(values_ordered);\n\n      // Take as many as we can, starting with the largest values\n      CUTLASS_PRAGMA_UNROLL\n      for (int i = values_ordered.size() - 1; i >= 0; i--) {\n        auto& e = values_ordered[i];\n\n        uint32_t ccount = uint2b_t(numPerCol >> 2 * e.parts.col);\n        if (!kBothWays) {\n          ccount = 0;\n        }\n        bool selected = ccount != 3 && (row_count < kMaxValuesPerRow[row]);\n        indices |= selected << (e.parts.col + 4 * row);\n        numPerCol |= (ccount + selected) << 2 * e.parts.col;\n        row_count += selected;\n      }\n    }\n    return indices;\n  }\n};\n\ntemplate <typename T>\nvoid named_algorithms(T callback) {\n  callback(LargestValuesGreedy<IdentityOp>(), \"largest_values_greedy\");\n  callback(LineByLine<IdentityOp, 1, 1, 2, 2, true>(), \"causal1122\");\n  callback(LineByLine<IdentityOp, 2, 2, 2, 2, false>(), \"largest_notranspose\");\n  callback(LineByLine<AbsOp, 2, 2, 2, 2, false>(), \"largest_abs_notranspose\");\n  callback(LargestValuesGreedy<AbsOp>(), \"largest_abs_values_greedy\");\n\n  // default one\n  callback(LargestValuesGreedy<IdentityOp>(), \"\");\n}\n\n} // namespace sp24\n} // namespace xformers\n"
  },
  {
    "path": "xformers/csrc/sparse24/gemm.cu",
    "content": "// BEGIN COPY-PASTE FROM PyTorch\n// https://github.com/pytorch/pytorch/blob/b937510a3f254fe0223b9b29235e0eb6e6da912a/aten/src/ATen/native/sparse/cuda/StructuredSparseLinearCUTLASS.cu\n// Some very small modifications, like we don't need to support uint8, and we\n// always have the meta-reordered available\n#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include <cutlass/cutlass.h>\n#include <cutlass/gemm/device/gemm_sparse.h>\n\n#include <tuple>\n#include <type_traits>\n\n#include \"pt_stable_utils.h\"\n\nnamespace {\n#define CUTLASS_STATUS_CHECK(status)         \\\n  {                                          \\\n    STD_TORCH_CHECK(                         \\\n        status == cutlass::Status::kSuccess, \\\n        \"Got CUTLASS error: \",               \\\n        cutlassGetStatusString(status));     \\\n  }\n\n// Wrapper function for CUTLASS sparse GEMM implementation, used\n// solely to simplify dispatching from _structured_sparse_linear()\n// function below.\ntemplate <\n    bool kIsMeta,\n    typename ElementInputA,\n    typename ElementInputB,\n    typename ElementOutput,\n    typename ElementAccumulator,\n    typename ElementComputeEpilogue,\n    typename ThreadblockShape,\n    typename WarpShape,\n    typename InstructionShape,\n    typename EpilogueOp,\n    typename LayoutInputA,\n    typename LayoutInputB>\ntorch::stable::Tensor two_four_sgemm_cutlass(\n    const torch::stable::Tensor& tensor_a,\n    const torch::headeronly::IntHeaderOnlyArrayRef::value_type& tensor_a_stride,\n    const torch::stable::Tensor& tensor_b,\n    const torch::headeronly::IntHeaderOnlyArrayRef::value_type& tensor_b_stride,\n    const torch::stable::Tensor& meta_reordered) {\n  // Fix CUTLASS sparse GEMM template arguments that are not\n  // provided as template argument of this function, and create an\n  // alias for particular instantiation of this template.\n  using LayoutOutput =\n      cutlass::layout::RowMajor; // Result of the operation will be provided in\n                                 // row-major format.\n  using MMAOp = cutlass::arch::OpClassTensorOp; // Tensor cores are to be used\n                                                // for maximum performance.\n  using SmArch =\n      cutlass::arch::Sm80; // Only CC 8.x devices are suported at the moment.\n  using SwizzleThreadBlock =\n      cutlass::gemm::threadblock::GemmIdentityThreadblockSwizzle<\n          3>; // This choice provides good performance\n              // across wide range of operand sizes.\n  constexpr int NumStages = 4; // This choice provides good performance across\n                               // wide range of operand sizes.\n  using Gemm = cutlass::gemm::device::SparseGemm<\n      ElementInputA,\n      LayoutInputA,\n      ElementInputB,\n      LayoutInputB,\n      ElementOutput,\n      LayoutOutput,\n      ElementAccumulator,\n      MMAOp,\n      SmArch,\n      ThreadblockShape,\n      WarpShape,\n      InstructionShape,\n      EpilogueOp,\n      SwizzleThreadBlock,\n      NumStages>;\n\n  // Datatype and layout of metadata matrix are inferred from sparse\n  // GEMM template.\n  using ElementInputE = typename Gemm::ElementE;\n  using ReorderedLayoutInputE = typename Gemm::LayoutE;\n\n  constexpr auto kSparse = Gemm::kSparse;\n  constexpr int kElementsPerElementE = Gemm::kElementsPerElementE;\n\n  // Operand sizes.\n  const int length_m = tensor_a.size(0);\n  const int length_k = tensor_b.size(0);\n  const int length_n = tensor_b.size(1);\n  const auto meta_ncols = length_k / kSparse / kElementsPerElementE;\n\n  // Check for current CUTLASS limitations w.r.t. input sizes.\n  constexpr auto input_a_is_half =\n      std::is_same<ElementInputA, cutlass::half_t>::value ||\n      std::is_same<ElementInputA, cutlass::bfloat16_t>::value;\n  STD_TORCH_CHECK(\n      length_m % 32 == 0,\n      \"torch._structured_sparse_linear: Number of rows of sparse matrix must \"\n      \"be divisible by 32\");\n  STD_TORCH_CHECK(\n      length_k % (input_a_is_half ? 64 : 128) == 0,\n      \"torch._structured_sparse_linear: Number of rows of dense matrix must \"\n      \"be divisible by \",\n      (input_a_is_half ? 64 : 128));\n  STD_TORCH_CHECK(\n      length_n % (input_a_is_half ? 8 : 16) == 0,\n      \"torch._structured_sparse_linear: Number of columns of dense matrix \"\n      \"must be divisible by \",\n      (input_a_is_half ? 8 : 16));\n\n  // Determine PyTorch datatype for the output matrix.\n  auto tensor_d_dtype = torch::headeronly::ScalarType::Char;\n  if (std::is_same<ElementOutput, int32_t>::value) {\n    tensor_d_dtype = torch::headeronly::ScalarType::Int;\n  } else if (std::is_same<ElementOutput, cutlass::half_t>::value) {\n    tensor_d_dtype = torch::headeronly::ScalarType::Half;\n  } else if (std::is_same<ElementOutput, cutlass::bfloat16_t>::value) {\n    tensor_d_dtype = torch::headeronly::ScalarType::BFloat16;\n  } else {\n    STD_TORCH_CHECK(\n        false,\n        \"torch._structured_sparse_linear: invalid sparse GEMM output \"\n        \"datatype encountered\");\n  }\n\n  // Create output matrix.\n  auto tensor_d = torch::stable::new_empty(\n      tensor_a,\n      {length_m, length_n},\n      /*dtype=*/tensor_d_dtype);\n  if (kIsMeta) {\n    return tensor_d;\n  }\n\n  // Prepare arguments for CUTLASS sparse GEMM kernel.\n  cutlass::gemm::GemmCoord problem_size(length_m, length_n, length_k);\n  LayoutInputA layout_a(tensor_a_stride);\n  LayoutInputB layout_b(tensor_b_stride);\n  LayoutOutput layout_d(tensor_d.stride(0));\n  auto tensor_a_device_ref = cutlass::TensorRef<ElementInputA, LayoutInputA>(\n      (ElementInputA*)tensor_a.data_ptr(), layout_a);\n  auto tensor_b_device_ref = cutlass::TensorRef<ElementInputB, LayoutInputB>(\n      (ElementInputB*)tensor_b.data_ptr(), layout_b);\n  auto tensor_d_device_ref = cutlass::TensorRef<ElementOutput, LayoutOutput>(\n      (ElementOutput*)tensor_d.data_ptr(), layout_d);\n  auto tensor_e_reordered_device_ref =\n      cutlass::TensorRef<ElementInputE, ReorderedLayoutInputE>(\n          (ElementInputE*)meta_reordered.data_ptr(),\n          ReorderedLayoutInputE::packed({length_m, meta_ncols}));\n  ElementComputeEpilogue alpha(1);\n  ElementComputeEpilogue beta(0);\n  constexpr int split_k_slices = 1;\n\n  // Create a tuple of CUTLASS sparse GEMM kernel arguments.\n  typename Gemm::Arguments arguments{\n      problem_size,\n      tensor_a_device_ref,\n      tensor_b_device_ref,\n      tensor_d_device_ref,\n      tensor_d_device_ref,\n      tensor_e_reordered_device_ref,\n      {alpha, beta},\n      split_k_slices};\n\n  cutlass::Status status;\n\n  // Create CUTLASS sparse GEMM kernel object.\n  Gemm gemm_op;\n\n  // Verify that sparse GEMM operation with given arguments can be\n  // performed by CUTLASS.\n  status = gemm_op.can_implement(arguments);\n  CUTLASS_STATUS_CHECK(status);\n\n  // Allocate workspace for CUTLASS sparse GEMM kernel.\n  const auto workspace_size = Gemm::get_workspace_size(arguments);\n  auto workspace = torch::stable::new_empty(\n      tensor_a, {(int64_t)workspace_size}, torch::headeronly::ScalarType::Byte);\n\n  // Initialize CUTLASS sparse GEMM object.\n  status = gemm_op.initialize(\n      arguments, workspace.data_ptr(), xf_getCurrentCUDAStream());\n  CUTLASS_STATUS_CHECK(status);\n\n  // Perform sparse GEMM operation.\n  status = gemm_op.run(xf_getCurrentCUDAStream());\n  CUTLASS_STATUS_CHECK(status);\n\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n\n  return tensor_d;\n}\n\ntemplate <bool kIsMeta>\ntorch::stable::Tensor _sparse24_gemm(\n    const torch::stable::Tensor& tensor_a,\n    const torch::stable::Tensor& tensor_b,\n    const torch::stable::Tensor& mask_or_meta) {\n  // No need to check that all tensors are on CUDA device, as this\n  // is provided by dispatch.\n\n  // For now, only CC 8.x devices are supported.\n  if (!kIsMeta) {\n    const auto dprops = xf_getCurrentDeviceProperties();\n    const auto is_sm8x = dprops->major == 8;\n    STD_TORCH_CHECK(\n        is_sm8x,\n        \"torch._structured_sparse_linear: Supported only on GPUs with \"\n        \"compute capability 8.x\");\n  }\n\n  // Validate layouts of input tensors.\n  STD_TORCH_CHECK(\n      !xf_is_sparse(tensor_a),\n      \"torch._structured_sparse_linear: Expected tensor_a argument \"\n      \"to be strided, but got layout \",\n      xf_get_layout(tensor_a));\n  STD_TORCH_CHECK(\n      tensor_a.dim() == 2,\n      \"torch._structured_sparse_linear: Expected tensor_a argument \"\n      \"to be 2D tensor, got \",\n      tensor_a.dim(),\n      \" dims\");\n  const auto strides_a = tensor_a.strides();\n  STD_TORCH_CHECK(\n      (strides_a[0] == 1 || strides_a[1] == 1) && strides_a[0] != strides_a[1],\n      \"torch._structured_sparse_linear: Invalid strides for tensor_a \"\n      \"argument: row stride = \",\n      strides_a[0],\n      \", column stride = \",\n      strides_a[1]);\n  STD_TORCH_CHECK(\n      !xf_is_sparse(tensor_b),\n      \"torch._structured_sparse_linear: Expected tensor_b argument \"\n      \"to be strided, but got layout \",\n      xf_get_layout(tensor_b));\n  STD_TORCH_CHECK(\n      tensor_b.dim() == 2,\n      \"torch._structured_sparse_linear: Expected tensor_b argument \"\n      \"to be 2D tensor, got \",\n      tensor_b.dim(),\n      \" dims\");\n  const auto strides_b = tensor_b.strides();\n  STD_TORCH_CHECK(\n      (strides_b[0] == 1 || strides_b[1] == 1) && strides_b[0] != strides_b[1],\n      \"torch._structured_sparse_linear: Invalid strides for tensor_b \"\n      \"argument: row stride = \",\n      strides_b[0],\n      \", column stride = \",\n      strides_b[1]);\n\n  // Determine layout (row-major or column-major) of input tensors.\n  auto tensor_a_row_major = strides_a[1] == 1;\n  auto tensor_a_stride = tensor_a_row_major ? strides_a[0] : strides_a[1];\n  auto tensor_b_row_major = strides_b[1] == 1;\n  auto tensor_b_stride = tensor_b_row_major ? strides_b[0] : strides_b[1];\n\n  // Call wrapper function for CUTLASS sparse GEMM, dispatching on\n  // the input datatype, and then on input tensors layouts.\n  // According to the input tensors datatypes and layouts,\n  // correspnding template arguments are supplied for instantiating\n  // the wrapper function.  The tile sizes template arguments are\n  // selected according to the CUTLASS profiler results, for number\n  // of runs.\n  torch::stable::Tensor result;\n  auto runGemm = [&](auto dtype) {\n    using ElementInputA = decltype(dtype);\n    using ElementInputB = decltype(dtype);\n    using ElementOutput = decltype(dtype);\n\n    using ElementAccumulator = float;\n    using ElementComputeEpilogue = float;\n    using ThreadblockShape = cutlass::gemm::GemmShape<256, 128, 64>;\n    using WarpShape = cutlass::gemm::GemmShape<64, 64, 64>;\n    using InstructionShape = cutlass::gemm::GemmShape<16, 8, 32>;\n    using EpilogueOp = cutlass::epilogue::thread::LinearCombination<\n        ElementOutput,\n        128 / cutlass::sizeof_bits<ElementOutput>::value,\n        ElementAccumulator,\n        ElementComputeEpilogue>;\n    if (tensor_a_row_major && tensor_b_row_major) {\n      result = two_four_sgemm_cutlass<\n          kIsMeta,\n          ElementInputA,\n          ElementInputB,\n          ElementOutput,\n          ElementAccumulator,\n          ElementComputeEpilogue,\n          ThreadblockShape,\n          WarpShape,\n          InstructionShape,\n          EpilogueOp,\n          cutlass::layout::RowMajor,\n          cutlass::layout::RowMajor>(\n          tensor_a, tensor_a_stride, tensor_b, tensor_b_stride, mask_or_meta);\n    } else if (tensor_a_row_major && !tensor_b_row_major) {\n      result = two_four_sgemm_cutlass<\n          kIsMeta,\n          ElementInputA,\n          ElementInputB,\n          ElementOutput,\n          ElementAccumulator,\n          ElementComputeEpilogue,\n          ThreadblockShape,\n          WarpShape,\n          InstructionShape,\n          EpilogueOp,\n          cutlass::layout::RowMajor,\n          cutlass::layout::ColumnMajor>(\n          tensor_a, tensor_a_stride, tensor_b, tensor_b_stride, mask_or_meta);\n    } else if (!tensor_a_row_major && tensor_b_row_major) {\n      result = two_four_sgemm_cutlass<\n          kIsMeta,\n          ElementInputA,\n          ElementInputB,\n          ElementOutput,\n          ElementAccumulator,\n          ElementComputeEpilogue,\n          ThreadblockShape,\n          WarpShape,\n          InstructionShape,\n          EpilogueOp,\n          cutlass::layout::ColumnMajor,\n          cutlass::layout::RowMajor>(\n          tensor_a, tensor_a_stride, tensor_b, tensor_b_stride, mask_or_meta);\n    } else if (!tensor_a_row_major && !tensor_b_row_major) {\n      result = two_four_sgemm_cutlass<\n          kIsMeta,\n          ElementInputA,\n          ElementInputB,\n          ElementOutput,\n          ElementAccumulator,\n          ElementComputeEpilogue,\n          ThreadblockShape,\n          WarpShape,\n          InstructionShape,\n          EpilogueOp,\n          cutlass::layout::ColumnMajor,\n          cutlass::layout::ColumnMajor>(\n          tensor_a, tensor_a_stride, tensor_b, tensor_b_stride, mask_or_meta);\n    }\n  };\n  if (tensor_a.scalar_type() == torch::headeronly::ScalarType::Half) {\n    runGemm(cutlass::half_t());\n  } else if (\n      tensor_a.scalar_type() == torch::headeronly::ScalarType::BFloat16) {\n    runGemm(cutlass::bfloat16_t());\n  } else {\n    STD_TORCH_CHECK(false, \"Unsupported Sparse24 GEMM\")\n  }\n  return result;\n}\n// END PyTorch copy-pasted code\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\"_sparse24_gemm\", TORCH_BOX(_sparse24_gemm<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\"_sparse24_gemm\", TORCH_BOX(_sparse24_gemm<true>));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/meta_utils.cu",
    "content": "#include <cutlass/array.h>\n#include <cutlass/bfloat16.h>\n#include <cutlass/coord.h>\n#include <cutlass/gemm/gemm.h>\n#include <cutlass/half.h>\n#include <cutlass/layout/matrix.h>\n#include <cutlass/tensor_ref.h>\n#include <cutlass/tensor_view.h>\n#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n#include <torch/headeronly/core/TensorAccessor.h>\n\n#include \"pt_stable_utils.h\"\n#include \"static_sort.h\"\n\nnamespace {\n// This is for 2:4 f16\nusing ElementInputE = uint16_t;\nusing LayoutInputE = cutlass::layout::RowMajor;\nusing ReorderedLayoutInputE = cutlass::layout::ColumnMajorInterleaved<2>;\n\nusing RefInp = typename cutlass::TensorRef<ElementInputE, LayoutInputE>;\nusing RefReordered =\n    typename cutlass::TensorRef<ElementInputE, ReorderedLayoutInputE>;\n\ntorch::stable::Tensor _sparse24_pack_mask(const torch::stable::Tensor input) {\n  STD_TORCH_CHECK(input.is_contiguous(), \"Expected contiguous tensor\");\n  STD_TORCH_CHECK(input.dim() == 2, \"Expected 2d tensor\");\n  STD_TORCH_CHECK(\n      input.size(0) % 32 == 0 && input.size(1) % 32 == 0,\n      \"Wrong dim, should be dividable by 32\");\n  STD_TORCH_CHECK(\n      input.scalar_type() == torch::headeronly::ScalarType::Bool,\n      \"Expected bool Tensor\");\n\n  torch::stable::Tensor packed = torch::stable::new_empty(\n      input,\n      {input.size(0), input.size(1) / 16},\n      torch::headeronly::ScalarType::Short);\n  auto input_a = torch::headeronly::HeaderOnlyTensorAccessor<bool, 2>(\n      input.mutable_data_ptr<bool>(),\n      input.sizes().data(),\n      input.strides().data());\n  auto packed_a = torch::headeronly::HeaderOnlyTensorAccessor<int16_t, 2>(\n      packed.mutable_data_ptr<int16_t>(),\n      packed.sizes().data(),\n      packed.strides().data());\n  for (int row = 0; row < input.size(0); ++row) {\n    for (int col_s = 0; col_s < input.size(1); col_s += 16) {\n      ElementInputE out = 0;\n      for (int bit_shifts = 0; bit_shifts < 16; bit_shifts += 4) {\n        int first_pos = -1;\n        int second_pos = -1;\n        for (int i = 0; i < 4; ++i) {\n          if (input_a[row][col_s + bit_shifts + i]) {\n            if (first_pos == -1) {\n              first_pos = i;\n            } else if (second_pos == -1) {\n              second_pos = i;\n            } else {\n              STD_TORCH_CHECK(\n                  second_pos != -1,\n                  \"Invalid mask at (\",\n                  row,\n                  \", \",\n                  col_s + bit_shifts,\n                  \"): too many values\");\n            }\n          }\n        }\n        STD_TORCH_CHECK(\n            second_pos != -1,\n            \"Invalid mask at (\",\n            row,\n            \", \",\n            col_s + bit_shifts,\n            \"): not enough values\");\n        out |= (first_pos | (second_pos * 4)) << bit_shifts;\n      }\n      packed_a[row][col_s / 16] = out;\n    }\n  }\n  return packed;\n}\n\n// Taken from <cutlass/tools/util/include/cutlass/util/host_reorder.h>\n// Can't include it directly as we have compilation errors...\ntemplate <typename Element, typename LayoutDest, typename LayoutSrc>\nvoid reorder_meta(\n    cutlass::TensorRef<Element, LayoutDest> dest,\n    cutlass::TensorRef<Element, LayoutSrc> src,\n    cutlass::gemm::GemmCoord problem_size) {\n  for (int m = 0; m < problem_size.m(); m++) {\n    for (int k = 0; k < problem_size.k(); k++) {\n      // First reorder the rows.\n      int group = (sizeof(Element) == 2) ? 32 : 16;\n      int interweave = (sizeof(Element) == 2) ? 4 : 2;\n\n      int dest_row = m / group * group + (m % 8) * interweave + (m % group) / 8;\n      int dest_col = k;\n\n      // Next swizzle the 2x2 blocks from Z to N.\n      if (((dest_row % 2) == 0) && ((dest_col % 2) == 1)) {\n        ++dest_row;\n        --dest_col;\n      } else if (((dest_row % 2) == 1) && ((dest_col % 2) == 0)) {\n        --dest_row;\n        ++dest_col;\n      }\n\n      dest.at({dest_row, dest_col}) = src.at({m, k});\n    }\n  }\n}\n\ntorch::stable::Tensor _sparse24_reorder_meta(torch::stable::Tensor input) {\n  STD_TORCH_CHECK(input.dim() == 2, \"Expected 2d tensor\");\n  STD_TORCH_CHECK(input.size(0) % 32 == 0, \"Wrong dim0\");\n  STD_TORCH_CHECK(input.size(1) % 2 == 0, \"Wrong dim1\");\n  STD_TORCH_CHECK(\n      input.scalar_type() == torch::headeronly::ScalarType::Short,\n      \"Expected int16 tensor\");\n  input = xf_contiguous(input);\n  cutlass::gemm::GemmCoord problem_size(input.size(0), 0, input.size(1));\n\n  cutlass::MatrixCoord meta_dim{input.size(0), input.size(1)};\n  auto reordered_layout = ReorderedLayoutInputE::packed(meta_dim);\n  torch::stable::Tensor reordered =\n      torch::stable::new_empty(input, {reordered_layout.capacity(meta_dim)});\n\n  RefInp ref_inp{(uint16_t*)input.data_ptr(), LayoutInputE(input.stride(0))};\n  RefReordered ref_reordered{(uint16_t*)reordered.data_ptr(), reordered_layout};\n\n  reorder_meta(ref_reordered, ref_inp, problem_size);\n  return xf_permute(\n      torch::stable::view(reordered, {input.size(1) / 2, input.size(0), 2}),\n      {1, 2, 0});\n}\n\ntorch::stable::Tensor _sparse24_pack_tensor_according_to_mask(\n    torch::stable::Tensor a,\n    torch::stable::Tensor meta_reordered) {\n  STD_TORCH_CHECK(a.dim() == 2, \"Expected 2d tensor\");\n  STD_TORCH_CHECK(a.size(0) % 32 == 0, \"Wrong dim0\");\n  STD_TORCH_CHECK(a.size(1) % 4 == 0, \"Wrong dim1\");\n  STD_TORCH_CHECK(\n      meta_reordered.dim() == 3, \"Expected meta to be reordered already\");\n\n  torch::stable::Tensor a_packed =\n      torch::stable::new_empty(a, {a.size(0), a.size(1) / 2});\n  cutlass::MatrixCoord meta_dim{\n      meta_reordered.size(0), meta_reordered.size(1) * meta_reordered.size(2)};\n  auto reordered_layout = ReorderedLayoutInputE::packed(meta_dim);\n  torch::stable::Tensor reordered =\n      torch::stable::new_empty(a, {reordered_layout.capacity(meta_dim)});\n  RefReordered ref_meta_reordered{\n      (uint16_t*)meta_reordered.data_ptr(), reordered_layout};\n  RefInp ref_a{(uint16_t*)a.data_ptr(), LayoutInputE(a.stride(0))};\n  RefInp ref_a_packed{\n      (uint16_t*)a_packed.data_ptr(), LayoutInputE(a_packed.stride(0))};\n\n  for (int m = 0; m < a.size(0); m++) {\n    for (int k = 0; k < a.size(1) / 16; k++) {\n      // First reorder the rows.\n      int group = (sizeof(ElementInputE) == 2) ? 32 : 16;\n      int interweave = (sizeof(ElementInputE) == 2) ? 4 : 2;\n\n      int dest_row = m / group * group + (m % 8) * interweave + (m % group) / 8;\n      int dest_col = k;\n\n      // Next swizzle the 2x2 blocks from Z to N.\n      if (((dest_row % 2) == 0) && ((dest_col % 2) == 1)) {\n        ++dest_row;\n        --dest_col;\n      } else if (((dest_row % 2) == 1) && ((dest_col % 2) == 0)) {\n        --dest_row;\n        ++dest_col;\n      }\n\n      uint16_t pack_info = ref_meta_reordered.at({dest_row, dest_col});\n      // For each group of 4, read the only 2 that are selected in the mask\n      for (int group_shift = 0; group_shift < 16; group_shift += 4) {\n        int pos0 = (pack_info >> group_shift) & 3;\n        int pos1 = (pack_info >> (group_shift + 2)) & 3;\n        ref_a_packed.at({m, 8 * k + group_shift / 2}) =\n            ref_a.at({m, 16 * k + group_shift + pos0});\n        ref_a_packed.at({m, 8 * k + group_shift / 2 + 1}) =\n            ref_a.at({m, 16 * k + group_shift + pos1});\n      }\n    }\n  }\n  return a_packed;\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CPU, m) {\n  m.impl(\"_sparse24_pack_mask\", TORCH_BOX(_sparse24_pack_mask));\n  m.impl(\"_sparse24_reorder_meta\", TORCH_BOX(_sparse24_reorder_meta));\n  m.impl(\n      \"_sparse24_pack_tensor_according_to_mask\",\n      TORCH_BOX(_sparse24_pack_tensor_according_to_mask));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24.cpp",
    "content": "#include <torch/csrc/stable/library.h>\n\nSTABLE_TORCH_LIBRARY_FRAGMENT(xformers, m) {\n  m.def(\"sparse24_largest_mask_2d(Tensor input) -> Tensor\");\n  m.def(\n      \"sparse24_largest_with_Krandom_mask_2d(Tensor input, int numRandom) -> Tensor\");\n  m.def(\"_sparse24_pack_mask(Tensor mask) -> Tensor\");\n  m.def(\"_sparse24_reorder_meta(Tensor mask) -> Tensor\");\n  m.def(\"_sparse24_gemm(Tensor a, Tensor b, Tensor meta_reordered) -> Tensor\");\n  m.def(\n      \"_sparse24_pack_tensor_according_to_mask(Tensor a, Tensor meta_reordered) -> Tensor\");\n  m.def(\n      \"sparse24_sparsify_both_ways(Tensor input, str algorithm = '', str backend = 'cutlass') -> (Tensor, Tensor, Tensor, Tensor, Tensor)\");\n  m.def(\n      \"_sparse24_meta_shuffle_test(Tensor local_meta, bool transposed) -> Tensor\");\n  m.def(\n      \"sparse24_apply(Tensor input, Tensor threads_masks, str backend = 'cutlass') -> (Tensor, Tensor, Tensor, Tensor)\");\n  m.def(\n      \"sparse24_apply_dense_output(Tensor input, Tensor threads_masks, float mul0=0.0, float mul1=1.0) -> Tensor\");\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_apply.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include \"pt_stable_utils.h\"\n#include \"sparse24_metadata.h\"\n#include \"sparse24_pack.h\"\n\nusing namespace xformers::sp24;\n\nnamespace {\n\ntemplate <typename KT>\n__global__ void __launch_bounds__(32 /* num_threads */)\n    sparse24_apply_kernel(typename KT::Params p) {\n  KT::sparse24_apply_kernel(p);\n}\n\n// Apply a 2:4 sparsify pattern computed with\n// `sparse24_sparsify_both_ways_kernel` to another Tensor\ntemplate <typename Element, typename MetadataFormat, bool kIsMeta>\nstd::\n    tuple<\n        torch::stable::Tensor, // packed\n        torch::stable::Tensor, // packed_meta_reordered\n        torch::stable::Tensor, // packed_trans\n        torch::stable::Tensor // packed_trans_meta_reordered\n        >\n    sparse24_apply_typed(\n        torch::stable::Tensor input, // Tensor to sparsify\n        torch::stable::Tensor\n            threads_masks // Returned by `sparse24_sparsify_both_ways`\n    ) {\n  using KT = KernelTypes<Element>;\n  // TODO: Technically we should be able to deal with that\n  // by running on the transpose of `input` and swapping\n  // `packed` & `packed_t`.\n  // This would require to adapt the `threads_masks` a bit tho.\n  if (input.stride(1) != 1) {\n    input = xf_contiguous(input);\n  }\n  std::optional<torch::stable::accelerator::DeviceGuard> device_guard;\n  if (!kIsMeta) {\n    device_guard.emplace(input.device().index());\n  }\n\n  STD_TORCH_CHECK(input.dim() == 2);\n  STD_TORCH_CHECK(input.stride(1) == 1);\n  STD_TORCH_CHECK(input.stride(0) % 8 == 0);\n  STD_TORCH_CHECK(input.size(1) % 32 == 0, \"Wrong alignment shape[1]\");\n\n  auto rows = input.size(0);\n  auto cols = input.size(1);\n\n  auto [compressed, packed, packed_meta_reordered] =\n      MetadataFormat::create_compressed_representation(\n          rows, cols, input, false);\n  auto [compressed_trans, packed_trans, packed_trans_meta_reordered] =\n      MetadataFormat::create_compressed_representation(\n          cols, rows, input, false);\n\n  typename KT::Params p;\n  p.input_s0 = input.stride(0);\n  p.input_dim0 = input.size(0);\n  p.input_dim1 = input.size(1);\n\n  p.packed_stride = packed.stride(0);\n  p.packed_trans_stride = packed_trans.stride(0);\n\n  if (!kIsMeta) {\n    p.input = (Element const*)input.data_ptr();\n    p.packed = (Element*)packed.data_ptr();\n    p.packed_trans = (Element*)packed_trans.data_ptr();\n    p.threads_masks = (uint64_t*)threads_masks.data_ptr();\n  }\n\n  STD_TORCH_CHECK(threads_masks.dim() == 3);\n  STD_TORCH_CHECK(\n      threads_masks.size(0) == p.getBlocksGrid().x * p.getThreadsGrid().x);\n  STD_TORCH_CHECK(\n      threads_masks.size(1) == p.getBlocksGrid().y * p.getThreadsGrid().y);\n  STD_TORCH_CHECK(threads_masks.stride(1) == sizeof(p.threads_masks[0]));\n  STD_TORCH_CHECK(threads_masks.size(2) == sizeof(p.threads_masks[0]));\n  STD_TORCH_CHECK(threads_masks.stride(2) == 1);\n  STD_TORCH_CHECK(\n      threads_masks.scalar_type() == torch::headeronly::ScalarType::Byte);\n\n  if (!kIsMeta) {\n    size_t smem_bytes = 0;\n    sparse24_apply_kernel<KT>\n        <<<p.getBlocksGrid(),\n           p.getThreadsGrid(),\n           smem_bytes,\n           xf_getCurrentCUDAStream()>>>(p);\n    STD_CUDA_KERNEL_LAUNCH_CHECK();\n  }\n  return std::make_tuple(\n      compressed,\n      packed_meta_reordered,\n      compressed_trans,\n      packed_trans_meta_reordered);\n}\n\ntemplate <bool kIsMeta>\nstd::\n    tuple<\n        torch::stable::Tensor, // packed\n        torch::stable::Tensor, // packed_meta_reordered\n        torch::stable::Tensor, // packed_trans\n        torch::stable::Tensor // packed_trans_meta_reordered\n        >\n    sparse24_apply(\n        torch::stable::Tensor input, // Tensor to sparsify\n        torch::stable::Tensor\n            threads_masks, // Returned by `sparse24_sparsify_both_ways`\n        std::string backend) {\n  auto runTyped = [&](auto type) {\n    using ElementT = decltype(type);\n    if (backend == \"cusparselt\") {\n      return sparse24_apply_typed<ElementT, MetadataCuSparseLtSm80, kIsMeta>(\n          input, threads_masks);\n    } else {\n      STD_TORCH_CHECK(\n          backend == \"cutlass\",\n          \"backend argument only supports `cutlass` or `cusparselt`\");\n      return sparse24_apply_typed<ElementT, MetadataCutlassSm80, kIsMeta>(\n          input, threads_masks);\n    }\n  };\n\n  if (input.scalar_type() == torch::headeronly::ScalarType::Half) {\n    return runTyped(cutlass::half_t());\n  } else {\n    STD_TORCH_CHECK(\n        input.scalar_type() == torch::headeronly::ScalarType::Half ||\n        input.scalar_type() == torch::headeronly::ScalarType::BFloat16);\n    return runTyped(cutlass::bfloat16_t());\n  }\n}\n\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\"sparse24_apply\", TORCH_BOX(sparse24_apply<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\"sparse24_apply\", TORCH_BOX(sparse24_apply<true>));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_apply_dense_output.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include \"compute_sparse_tile.h\"\n#include \"pt_stable_utils.h\"\n#include \"sparse24_pack.h\"\n\nusing namespace xformers::sp24;\n\nnamespace {\ntemplate <typename T>\nstruct Params {\n  uint64_t const* threads_masks;\n\n  T const* input;\n  int64_t input_stride;\n  int64_t input_dim0;\n  int64_t input_dim1;\n\n  T* output;\n  int64_t output_stride;\n\n  T mul0;\n  T mul1;\n\n  __host__ dim3 getBlocksGrid() const {\n    return dim3(\n        cutlass::ceil_div(input_dim0, kWarpX),\n        cutlass::ceil_div(input_dim1, kWarpY),\n        1);\n  }\n\n  static CUTLASS_HOST_DEVICE dim3 getThreadsGrid() {\n    return dim3(kWarpX / kThreadX, kWarpY / kThreadY, 1);\n  }\n\n  CUTLASS_DEVICE Tile8x8Masks* getCurrentThreadIndices() const {\n    Tile8x8Masks* gmem_threads_masks = (Tile8x8Masks*)threads_masks;\n    gmem_threads_masks += blockIdx.y * getThreadsGrid().y + threadIdx.y;\n    int64_t strideX = gridDim.y * getThreadsGrid().y;\n    gmem_threads_masks +=\n        (blockIdx.x * getThreadsGrid().x + threadIdx.x) * strideX;\n    return gmem_threads_masks;\n  }\n};\n\ntemplate <typename T, bool kInputRowMajor = true, bool kOutputRowMajor = true>\n__global__ void __launch_bounds__(32 /* num_threads */)\n    sparse24_apply_dense_output_k(Params<T> p) {\n  using Fragment = cutlass::Array<T, 8>;\n\n  // Top-left of the 8x8 tile we own\n  int warp_x = blockIdx.x * kWarpX;\n  int warp_y = blockIdx.y * kWarpY;\n  int x = warp_x + threadIdx.x * kThreadX;\n  int y = warp_y + threadIdx.y * kThreadY;\n\n  T* output = p.output + x * p.output_stride + y;\n  Tile8x8Masks indices = *p.getCurrentThreadIndices();\n\n  // Load dense\n  Fragment lines[8];\n  if (kInputRowMajor) {\n    T const* input = p.input + x * p.input_stride + y;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 8; ++i) {\n      cutlass::arch::global_load<Fragment, sizeof(Fragment)>(\n          lines[i], input + i * p.input_stride, true);\n    }\n  } else {\n    T const* input = p.input + x + y * p.input_stride;\n    Fragment columns[8];\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 8; ++i) {\n      cutlass::arch::global_load<Fragment, sizeof(Fragment)>(\n          columns[i], input + i * p.input_stride, true);\n    }\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 8; ++i) {\n      CUTLASS_PRAGMA_UNROLL\n      for (int j = 0; j < 8; ++j) {\n        lines[i][j] = columns[j][i].get();\n      }\n    }\n  }\n\n  CUTLASS_PRAGMA_UNROLL\n  for (int row = 0; row < 2; ++row) {\n    Indices4x4 masks[2];\n    if (row == 0) {\n      masks[0] = indices.a;\n      masks[1] = indices.b;\n    } else {\n      masks[0] = indices.c;\n      masks[1] = indices.d;\n    }\n\n    // Apply mask\n    CUTLASS_PRAGMA_UNROLL\n    for (int m = 0; m < 2; ++m) {\n      CUTLASS_PRAGMA_UNROLL\n      for (int r = 0; r < 4; ++r) {\n        CUTLASS_PRAGMA_UNROLL\n        for (int c = 0; c < 4; ++c) {\n          if ((masks[m] >> (4 * r + c)) & 1) {\n            lines[4 * row + r][4 * m + c] =\n                p.mul1 * lines[4 * row + r][4 * m + c];\n          } else {\n            lines[4 * row + r][4 * m + c] =\n                p.mul0 * lines[4 * row + r][4 * m + c];\n          }\n        }\n      }\n    }\n  }\n  static_assert(kOutputRowMajor, \"Transpose here for ColMajor output\");\n  // Save dense with zeros\n  CUTLASS_PRAGMA_UNROLL\n  for (int i = 0; i < 8; ++i) {\n    cutlass::arch::global_store<Fragment, sizeof(Fragment)>(\n        lines[i], output + i * p.output_stride, true);\n  }\n}\n\ntemplate <typename T, bool kIsMeta = false>\ntorch::stable::Tensor sparse24_apply_dense_output_typed(\n    torch::stable::Tensor input,\n    torch::stable::Tensor threads_masks,\n    float mul0,\n    float mul1) {\n  STD_TORCH_CHECK(\n      input.stride(0) == 1 || input.stride(1) == 1,\n      \"`input` should be either RowMajor or ColMajor. Invalid memory layout - try .contiguous()?\");\n\n  auto roundedx = cutlass::round_up(input.size(0), kWarpX);\n  auto roundedy = cutlass::round_up(input.size(1), kWarpY);\n\n  Params<T> p;\n  p.input_dim0 = input.size(0);\n  p.input_dim1 = input.size(1);\n  if (!kIsMeta) {\n    p.input = (T const*)input.data_ptr();\n    p.threads_masks = (uint64_t const*)threads_masks.data_ptr();\n  }\n\n  STD_TORCH_CHECK(threads_masks.dim() == 3);\n  STD_TORCH_CHECK(\n      threads_masks.size(0) == p.getBlocksGrid().x * p.getThreadsGrid().x);\n  STD_TORCH_CHECK(\n      threads_masks.size(1) == p.getBlocksGrid().y * p.getThreadsGrid().y);\n  STD_TORCH_CHECK(threads_masks.stride(1) == sizeof(p.threads_masks[0]));\n  STD_TORCH_CHECK(threads_masks.size(2) == sizeof(p.threads_masks[0]));\n  STD_TORCH_CHECK(threads_masks.stride(2) == 1);\n  STD_TORCH_CHECK(\n      threads_masks.scalar_type() == torch::headeronly::ScalarType::Byte);\n\n  torch::stable::Tensor output =\n      torch::stable::new_empty(input, {input.size(0), input.size(1)});\n  STD_TORCH_CHECK(output.stride(-1) == 1, \"expected RowMajor?\");\n  if (kIsMeta) {\n    return output;\n  }\n  p.output = (T*)output.data_ptr();\n  p.mul0 = T(mul0);\n  p.mul1 = T(mul1);\n\n  bool inputRowMajor = input.stride(-1) == 1;\n  bool outputRowMajor = output.stride(-1) == 1;\n  p.input_stride = input.stride(inputRowMajor ? 0 : 1);\n  p.output_stride = output.stride(outputRowMajor ? 0 : 1);\n  torch::stable::accelerator::DeviceGuard device_guard(input.device().index());\n\n  cudaStream_t stream = xf_getCurrentCUDAStream();\n  size_t smem_bytes = 0;\n  if (inputRowMajor && outputRowMajor) {\n    sparse24_apply_dense_output_k<T, true, true>\n        <<<p.getBlocksGrid(), p.getThreadsGrid(), smem_bytes, stream>>>(p);\n  } else if (!inputRowMajor && outputRowMajor) {\n    sparse24_apply_dense_output_k<T, false, true>\n        <<<p.getBlocksGrid(), p.getThreadsGrid(), smem_bytes, stream>>>(p);\n  } else {\n    STD_TORCH_CHECK(\n        false,\n        \"Unsupported configuration: `input` is \",\n        inputRowMajor ? \"RowMajor\" : \"ColMajor\",\n        \", and `output` is \",\n        outputRowMajor ? \"RowMajor\" : \"ColMajor\");\n  }\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n  return output;\n}\n\ntemplate <bool kIsMeta = false>\ntorch::stable::Tensor sparse24_apply_dense_output(\n    torch::stable::Tensor input,\n    torch::stable::Tensor threads_masks,\n    double mul0,\n    double mul1) {\n  STD_TORCH_CHECK(\n      input.scalar_type() == torch::headeronly::ScalarType::Half ||\n          input.scalar_type() == torch::headeronly::ScalarType::BFloat16,\n      \"Unsupported `input` dtype\");\n  if (input.scalar_type() == torch::headeronly::ScalarType::Half) {\n    return sparse24_apply_dense_output_typed<cutlass::half_t, kIsMeta>(\n        input, threads_masks, mul0, mul1);\n  } else {\n    return sparse24_apply_dense_output_typed<cutlass::bfloat16_t, kIsMeta>(\n        input, threads_masks, mul0, mul1);\n  }\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      \"sparse24_apply_dense_output\",\n      TORCH_BOX(sparse24_apply_dense_output<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\n      \"sparse24_apply_dense_output\",\n      TORCH_BOX(sparse24_apply_dense_output<true>));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_gemm_sm90.cu",
    "content": "#include <tuple>\n#include <type_traits>\n\n#include <cute/algorithm/functional.hpp>\n#include <cutlass/gemm/device/gemm_universal_adapter.h>\n#include <cutlass/util/packed_stride.hpp>\n#include \"cutlass/arch/wmma.h\"\n#include \"cutlass/bfloat16.h\"\n#include \"cutlass/cuda_host_adapter.hpp\"\n#include \"cutlass/cutlass.h\"\n#include \"cutlass/epilogue/collective/collective_builder.hpp\"\n#include \"cutlass/gemm/collective/collective_builder.hpp\"\n#include \"cutlass/gemm/gemm.h\"\n#include \"cutlass/gemm/kernel/gemm_universal.hpp\"\n#include \"cutlass/numeric_types.h\"\n#include \"cutlass/transform/device/transform_universal_adapter.hpp\"\n#include \"cutlass/transform/kernel/sparse_gemm_compressor.hpp\"\n\n#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include \"pt_stable_utils.h\"\n\n// CUTLASS does not mix well with Windows\n#ifndef _WIN32\n#if __CUDACC_VER_MAJOR__ > 12 || \\\n    (__CUDACC_VER_MAJOR__ == 12 && __CUDACC_VER_MINOR__ > 0)\n\n#ifdef CUTLASS_SM90_COLLECTIVE_BUILDER_SUPPORTED\nnamespace {\n#define CUTLASS_STATUS_CHECK(status)              \\\n  {                                               \\\n    STD_TORCH_CHECK(                              \\\n        status == cutlass::Status::kSuccess,      \\\n        \"Got CUTLASS error: \",                    \\\n        cutlass::cutlassGetStatusString(status)); \\\n  }\n\ntemplate <typename T>\nstruct identity {\n  CUTLASS_HOST_DEVICE\n  T operator()(T lhs) const {\n    return lhs;\n  }\n};\n\ntemplate <typename ElementA>\nstruct SparseRowwiseKernel;\n\ntemplate <>\nstruct SparseRowwiseKernel<cutlass::float_e4m3_t> {\n  static constexpr auto kElementOutAt = torch::headeronly::ScalarType::BFloat16;\n  static constexpr auto kElementAAt =\n      torch::headeronly::ScalarType::Float8_e4m3fn;\n\n  using ElementA = cutlass::float_e4m3_t;\n  using ElementB = cutlass::float_e4m3_t;\n  using ElementOut = cutlass::bfloat16_t;\n  using ElementAccumulator = float;\n\n  using TileShape = cute::Shape<cute::_128, cute::_256, cute::_128>;\n\n  // Epilogue visitor tree\n  using Accum = cutlass::epilogue::fusion::Sm90AccFetch;\n  using AScale =\n      cutlass::epilogue::fusion::Sm90ColBroadcast<0, TileShape, float>;\n  using BScale =\n      cutlass::epilogue::fusion::Sm90RowBroadcast<0, TileShape, float>;\n  using Multiply = cutlass::epilogue::fusion::Sm90Compute<\n      cutlass::multiplies,\n      float,\n      float,\n      cutlass::FloatRoundStyle::round_to_nearest>;\n  using Cast = cutlass::epilogue::fusion::Sm90Compute<\n      identity,\n      ElementOut,\n      float,\n      cutlass::FloatRoundStyle::round_to_nearest>;\n  using EpilogueEVT = cutlass::epilogue::fusion::Sm90EVT<\n      Cast,\n      cutlass::epilogue::fusion::Sm90EVT<\n          Multiply,\n          BScale,\n          cutlass::epilogue::fusion::Sm90EVT<Multiply, AScale, Accum>>>;\n\n  using CollectiveEpilogue =\n      typename cutlass::epilogue::collective::CollectiveBuilder<\n          cutlass::arch::Sm90,\n          cutlass::arch::OpClassSparseTensorOp,\n          TileShape,\n          cute::Shape<cute::_2, cute::_1, cute::_1>,\n          cutlass::epilogue::collective::EpilogueTileAuto,\n          ElementAccumulator,\n          float,\n          ElementOut,\n          cutlass::layout::RowMajor,\n          8,\n          ElementOut,\n          cutlass::layout::RowMajor,\n          8,\n          cutlass::epilogue::TmaWarpSpecializedCooperative,\n          EpilogueEVT>::CollectiveOp;\n\n  using CollectiveMainloop =\n      typename cutlass::gemm::collective::CollectiveBuilder<\n          cutlass::arch::Sm90,\n          cutlass::arch::OpClassSparseTensorOp,\n          ElementA,\n          cutlass::layout::RowMajor,\n          32,\n          ElementB,\n          cutlass::layout::ColumnMajor,\n          16,\n          ElementAccumulator,\n          cute::Shape<cute::_128, cute::_256, cute::_128>,\n          cute::Shape<cute::_2, cute::_1, cute::_1>,\n          cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(\n              sizeof(typename CollectiveEpilogue::SharedStorage))>,\n          cutlass::gemm::KernelTmaWarpSpecializedCooperativeFP8FastAccum>::\n          CollectiveOp;\n\n  // Gemm operator\n  // cutlass3x_sm90_sptensorop_s64x256x64spgemm_e4m3_e4m3_f32_bf16_bf16_128x256x128_2x1x1_0_tnt_align32_warpspecialized_cooperative_fp8_fastaccum_epi_tma\n  using GemmKernel = cutlass::gemm::kernel::GemmUniversal<\n      cute::Shape<int, int, int, int>,\n      CollectiveMainloop,\n      CollectiveEpilogue,\n      cutlass::gemm::PersistentScheduler>;\n  using ElementE = CollectiveMainloop::ElementE;\n};\n\ntemplate <>\nstruct SparseRowwiseKernel<cutlass::bfloat16_t> {\n  static constexpr auto kElementOutAt = torch::headeronly::ScalarType::BFloat16;\n  static constexpr auto kElementAAt = torch::headeronly::ScalarType::BFloat16;\n\n  using ElementA = cutlass::bfloat16_t;\n  using ElementB = cutlass::bfloat16_t;\n  using ElementOut = cutlass::bfloat16_t;\n\n  using TileShape = cute::Shape<cute::_128, cute::_128, cute::_64>;\n\n  // Epilogue visitor tree\n  using Accum = cutlass::epilogue::fusion::Sm90AccFetch;\n  using AScale =\n      cutlass::epilogue::fusion::Sm90ColBroadcast<0, TileShape, float>;\n  using BScale =\n      cutlass::epilogue::fusion::Sm90RowBroadcast<0, TileShape, float>;\n  using Multiply = cutlass::epilogue::fusion::Sm90Compute<\n      cutlass::multiplies,\n      float,\n      float,\n      cutlass::FloatRoundStyle::round_to_nearest>;\n  using Cast = cutlass::epilogue::fusion::Sm90Compute<\n      identity,\n      ElementOut,\n      float,\n      cutlass::FloatRoundStyle::round_to_nearest>;\n  using EpilogueEVT = cutlass::epilogue::fusion::Sm90EVT<\n      Cast,\n      cutlass::epilogue::fusion::Sm90EVT<\n          Multiply,\n          BScale,\n          cutlass::epilogue::fusion::Sm90EVT<Multiply, AScale, Accum>>>;\n\n  using CollectiveEpilogue =\n      typename cutlass::epilogue::collective::CollectiveBuilder<\n          cutlass::arch::Sm90,\n          cutlass::arch::OpClassSparseTensorOp,\n          TileShape,\n          cute::Shape<cute::_2, cute::_1, cute::_1>,\n          cutlass::epilogue::collective::EpilogueTileAuto,\n          float,\n          float,\n          ElementOut,\n          cutlass::layout::RowMajor,\n          8,\n          ElementOut,\n          cutlass::layout::RowMajor,\n          8,\n          cutlass::epilogue::TmaWarpSpecializedCooperative,\n          EpilogueEVT>::CollectiveOp;\n\n  using CollectiveMainloop =\n      typename cutlass::gemm::collective::CollectiveBuilder<\n          cutlass::arch::Sm90,\n          cutlass::arch::OpClassSparseTensorOp,\n          ElementA,\n          cutlass::layout::RowMajor,\n          16,\n          ElementB,\n          cutlass::layout::ColumnMajor,\n          16,\n          float,\n          cute::Shape<cute::_128, cute::_128, cute::_64>,\n          cute::Shape<cute::_2, cute::_1, cute::_1>,\n          cutlass::gemm::collective::StageCountAutoCarveout<static_cast<int>(\n              sizeof(typename CollectiveEpilogue::SharedStorage))>,\n          cutlass::gemm::KernelTmaWarpSpecializedCooperative>::CollectiveOp;\n\n  // Gemm operator\n  // cutlass3x_sm90_sptensorop_s64x128x32spgemm_bf16_bf16_f32_void_f32_128x128x64_2x1x1_0_ttn_align16_warpspecialized_cooperative_epi_tma\n  using GemmKernel = cutlass::gemm::kernel::GemmUniversal<\n      cute::Shape<int, int, int, int>,\n      CollectiveMainloop,\n      CollectiveEpilogue,\n      cutlass::gemm::PersistentScheduler>;\n  using ElementE = CollectiveMainloop::ElementE;\n};\n\ntemplate <bool kIsMeta>\ntorch::stable::Tensor _sparse24_fp8_sm90_cutlass_gemm(\n    const torch::stable::Tensor& tensor_a,\n    const torch::stable::Tensor& tensor_e, // metadata for `A`\n    const torch::stable::Tensor& tensor_b,\n    // *,\n    std::optional<torch::stable::Tensor> a_scale,\n    std::optional<torch::stable::Tensor> b_scale,\n    int64_t swizzle_size,\n    std::string swizzle_axis,\n    int64_t sm_count) {\n  std::optional<torch::stable::accelerator::DeviceGuard> device_guard;\n  if (!kIsMeta) {\n    device_guard.emplace(tensor_a.device().index());\n  }\n\n  using K = SparseRowwiseKernel<cutlass::float_e4m3_t>;\n\n  // For now, only CC 9.x devices are supported.\n  if (!kIsMeta) {\n    const auto dprops = xf_getCurrentDeviceProperties();\n    STD_TORCH_CHECK(\n        dprops && dprops->major == 9,\n        \"_sparse24_gemm_fp8_sm90: Supported only on GPUs with \"\n        \"compute capability 9.x\");\n  }\n\n  // Validate layouts of input tensors.\n  STD_TORCH_CHECK(tensor_a.device() == tensor_b.device());\n  STD_TORCH_CHECK(tensor_a.device() == tensor_e.device());\n  STD_TORCH_CHECK(tensor_a.dim() == 2);\n  STD_TORCH_CHECK(tensor_b.dim() == 2);\n  STD_TORCH_CHECK(tensor_a.scalar_type() == tensor_b.scalar_type());\n  STD_TORCH_CHECK(tensor_a.scalar_type() == K::kElementAAt);\n  STD_TORCH_CHECK(tensor_b.stride(0) == 1, \"B must be Row-Major\");\n  STD_TORCH_CHECK(tensor_a.is_contiguous());\n  STD_TORCH_CHECK(torch::stable::transpose(tensor_b, 0, 1).is_contiguous());\n  int64_t a_rows = tensor_a.size(0);\n  if (a_scale.has_value()) {\n    STD_TORCH_CHECK(a_scale->is_contiguous());\n    STD_TORCH_CHECK(\n        a_scale->scalar_type() == torch::headeronly::ScalarType::Float);\n    STD_TORCH_CHECK(a_scale->device() == tensor_a.device());\n    STD_TORCH_CHECK(a_scale->dim() == 2);\n    STD_TORCH_CHECK(a_scale->size(0) == a_rows);\n    STD_TORCH_CHECK(a_scale->size(1) == 1);\n  }\n  if (b_scale.has_value()) {\n    STD_TORCH_CHECK(b_scale->is_contiguous());\n    STD_TORCH_CHECK(\n        b_scale->scalar_type() == torch::headeronly::ScalarType::Float);\n    STD_TORCH_CHECK(b_scale->device() == tensor_b.device());\n    STD_TORCH_CHECK(b_scale->dim() == 2);\n    STD_TORCH_CHECK(b_scale->size(0) == 1);\n    STD_TORCH_CHECK(b_scale->size(1) == tensor_b.size(1));\n  }\n\n  typename K::GemmKernel::Arguments args;\n  args.mode = cutlass::gemm::GemmUniversalMode::kGemm;\n  args.problem_shape = cute::make_shape(\n      int(a_rows), int(tensor_b.size(1)), int(tensor_b.size(0)), 1);\n  torch::stable::Tensor out = torch::stable::new_empty(\n      tensor_a,\n      {cute::get<0>(args.problem_shape), cute::get<1>(args.problem_shape)},\n      /*dtype=*/K::kElementOutAt);\n\n  args.mainloop.ptr_A =\n      reinterpret_cast<K::ElementA const*>(tensor_a.data_ptr());\n  args.mainloop.ptr_B = static_cast<K::ElementB const*>(tensor_b.data_ptr());\n  args.mainloop.ptr_E =\n      reinterpret_cast<K::ElementE const*>(tensor_e.data_ptr());\n  args.epilogue.ptr_C = nullptr;\n  args.epilogue.ptr_D = static_cast<K::ElementOut*>(out.data_ptr());\n\n  float const* a_scale_ptr =\n      (float const*)(a_scale.has_value() ? a_scale->data_ptr() : nullptr);\n  float const* b_scale_ptr =\n      (float const*)(b_scale.has_value() ? b_scale->data_ptr() : nullptr);\n  float default_scale = 1.0f; // used if ptr is nullptr\n  auto& cast_op = args.epilogue.thread;\n  auto& mulB_op = cast_op.op_0;\n  mulB_op.op_0 = {b_scale_ptr, default_scale};\n  auto& mulA_op = mulB_op.op_1;\n  mulA_op.op_0 = {a_scale_ptr, default_scale};\n\n  args.mainloop.layout_a =\n      K::CollectiveMainloop::SparseConfig::fill_layoutA(args.problem_shape);\n  args.mainloop.layout_e =\n      K::CollectiveMainloop::SparseConfig::fill_layoutE(args.problem_shape);\n  args.mainloop.dB = cute::make_int_tuple_from<typename K::GemmKernel::StrideB>(\n      tensor_b.stride(1), 0);\n  args.epilogue.dC = cute::make_int_tuple_from<typename K::GemmKernel::StrideC>(\n      out.stride(0), 0);\n  args.epilogue.dD = cute::make_int_tuple_from<typename K::GemmKernel::StrideD>(\n      out.stride(0), 0);\n\n  /* Query device SM count to pass onto the kernel as an argument, where needed\n   */\n  args.hw_info.device_id = tensor_a.device().index();\n  args.hw_info.sm_count = sm_count;\n  args.scheduler.max_swizzle_size = swizzle_size;\n  using Enum_t = decltype(args.scheduler.raster_order);\n  if (swizzle_axis == \"n\") {\n    args.scheduler.raster_order = Enum_t::AlongN;\n  } else {\n    STD_TORCH_CHECK(\n        swizzle_axis == \"m\",\n        \"Invalid value for swizzle_axis ('\",\n        swizzle_axis,\n        \"')\");\n    args.scheduler.raster_order = Enum_t::AlongM;\n  }\n\n  using Gemm = cutlass::gemm::device::GemmUniversalAdapter<K::GemmKernel>;\n  int64_t device_op_workspace_size = Gemm::get_workspace_size(args);\n  torch::stable::Tensor workspace = torch::stable::new_empty(\n      tensor_a,\n      {device_op_workspace_size},\n      torch::headeronly::ScalarType::Byte);\n\n  Gemm gemm;\n  // Check the problem size is supported or not\n  CUTLASS_STATUS_CHECK(gemm.can_implement(args));\n\n  auto status =\n      gemm.run(args, (void*)workspace.data_ptr(), xf_getCurrentCUDAStream());\n  CUTLASS_STATUS_CHECK(status);\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n  return out;\n}\n\ntemplate <bool kIsMeta, typename ElementT>\nstd::tuple<torch::stable::Tensor, torch::stable::Tensor>\n_sparse24_sm90_cutlass_compress_t(torch::stable::Tensor a) {\n  std::optional<torch::stable::accelerator::DeviceGuard> device_guard;\n  if (!kIsMeta) {\n    device_guard.emplace(a.device().index());\n  }\n\n  using K = SparseRowwiseKernel<ElementT>;\n  STD_TORCH_CHECK(a.scalar_type() == K::kElementAAt);\n  STD_TORCH_CHECK(a.is_contiguous());\n\n  // Offline compressor kernel\n  using LayoutA = cutlass::layout::RowMajor;\n  using ProblemShape = cute::Shape<int, int, int, int>;\n  using SparseConfig = typename K::CollectiveMainloop::SparseConfig;\n  using CompressorUtility =\n      cutlass::transform::kernel::StructuredSparseCompressorUtility<\n          ProblemShape,\n          typename K::ElementA,\n          LayoutA,\n          SparseConfig>;\n\n  using CompressorKernel =\n      cutlass::transform::kernel::StructuredSparseCompressor<\n          ProblemShape,\n          typename K::ElementA,\n          LayoutA,\n          SparseConfig,\n          cutlass::arch::Sm90>;\n\n  using Compressor =\n      cutlass::transform::device::TransformUniversalAdapter<CompressorKernel>;\n\n  auto problem_shape =\n      cute::make_shape(int(a.size(0)), 8192, int(a.size(1)), 1);\n  auto [M, N, k, L] = problem_shape;\n  auto stride_A = cutlass::make_cute_packed_stride(\n      cutlass::gemm::TagToStrideA_t<LayoutA>{}, cute::make_shape(M, k, L));\n  CompressorUtility compressor_utility(problem_shape, stride_A);\n\n  int ME = compressor_utility.get_metadata_m_physical();\n  int KE = compressor_utility.get_metadata_k_physical();\n  int KC = compressor_utility.get_tensorA_k_physical();\n\n  auto a_compressed = torch::stable::new_empty(a, {M, KC * L});\n  auto e = torch::stable::new_empty(\n      a, {ME * KE * L}, torch::headeronly::ScalarType::Byte);\n\n  if (kIsMeta) {\n    return std::make_tuple(a_compressed, e);\n  }\n\n  cutlass::KernelHardwareInfo hw_info;\n  hw_info.device_id = a.device().index();\n  hw_info.sm_count = 128;\n  typename Compressor::Arguments arguments{\n      problem_shape,\n      {(typename K::ElementA const*)a.data_ptr(),\n       stride_A,\n       (typename K::ElementA*)a_compressed.data_ptr(),\n       (typename K::ElementE*)e.data_ptr()},\n      {hw_info}};\n\n  Compressor compressor_op;\n  int64_t workspace_size = Compressor::get_workspace_size(arguments);\n  torch::stable::Tensor workspace = torch::stable::new_empty(\n      a, {workspace_size}, torch::headeronly::ScalarType::Byte);\n\n  CUTLASS_STATUS_CHECK(compressor_op.can_implement(arguments));\n  CUTLASS_STATUS_CHECK(\n      compressor_op.initialize(arguments, workspace.data_ptr()));\n  CUTLASS_STATUS_CHECK(compressor_op.run());\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n\n  return std::make_tuple(a_compressed, e);\n}\n\ntemplate <bool kIsMeta>\nstd::tuple<torch::stable::Tensor, torch::stable::Tensor>\n_sparse24_sm90_cutlass_compress(torch::stable::Tensor a) {\n  if (a.scalar_type() == torch::headeronly::ScalarType::Float8_e4m3fn) {\n    return _sparse24_sm90_cutlass_compress_t<kIsMeta, cutlass::float_e4m3_t>(a);\n  }\n  if (a.scalar_type() == torch::headeronly::ScalarType::BFloat16) {\n    return _sparse24_sm90_cutlass_compress_t<kIsMeta, cutlass::bfloat16_t>(a);\n  }\n  STD_TORCH_CHECK(false, \"Unsupported dtype for operand\");\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_FRAGMENT(xformers, m) {\n  m.def(\n      \"_sparse24_fp8_sm90_cutlass_gemm(Tensor a, Tensor a_mdata, Tensor b, *, Tensor? a_scale = None, Tensor? b_scale = None, int swizzle_size=8, str swizzle_axis='n', int sm_count=128) -> Tensor\");\n  m.def(\"_sparse24_sm90_cutlass_compress(Tensor a) -> (Tensor, Tensor)\");\n}\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      \"_sparse24_fp8_sm90_cutlass_gemm\",\n      TORCH_BOX(_sparse24_fp8_sm90_cutlass_gemm<false>));\n  m.impl(\n      \"_sparse24_sm90_cutlass_compress\",\n      TORCH_BOX(_sparse24_sm90_cutlass_compress<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\n      \"_sparse24_fp8_sm90_cutlass_gemm\",\n      TORCH_BOX(_sparse24_fp8_sm90_cutlass_gemm<true>));\n  m.impl(\n      \"_sparse24_sm90_cutlass_compress\",\n      TORCH_BOX(_sparse24_sm90_cutlass_compress<true>));\n}\n#endif\n#endif\n#endif\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_largest_mask_2d.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include <cutlass/array.h>\n#include <cutlass/bfloat16.h>\n#include <cutlass/half.h>\n\n#include \"pt_stable_utils.h\"\n#include \"static_sort.h\"\n\ntemplate <typename Element, bool kHasRandom>\nstruct Sp24MaskKernel {\n  // thread-level tiling\n  static constexpr int kNumRows = 4;\n  static constexpr int kNumCols = 4;\n  static constexpr int kMaxPerRow = 2;\n  static constexpr int kMaxPerCol = 2;\n  static constexpr int kTakeMaximumCount = kNumRows * kMaxPerRow;\n  // block-level tiling\n  static constexpr int kRowsPerBlock = 32;\n  static constexpr int kColsPerBlock = 16;\n  static constexpr int kNumThreadsPerBlock =\n      (kRowsPerBlock / kNumRows) * (kRowsPerBlock / kNumRows);\n  static_assert(kNumThreadsPerBlock % 32 == 0, \"invalid warp size\");\n\n  struct PseudoRandomOrder {\n    uint8_t row;\n    uint8_t col;\n    uint16_t random_score;\n\n    CUTLASS_DEVICE bool operator<(PseudoRandomOrder const& other) const {\n      return random_score < other.random_score;\n    }\n  };\n\n  struct ElementWithPos {\n    Element e;\n    uint8_t row;\n    uint8_t col;\n\n    CUTLASS_DEVICE bool operator<(ElementWithPos const& other) const {\n      return e < other.e;\n    }\n  };\n\n  struct Params {\n    // Inputs assumed in RowMajor\n    Element const* input;\n    Element* output;\n    int64_t stride0;\n    int64_t size0;\n    int64_t size1;\n    int8_t numRandom;\n\n    CUTLASS_HOST_DEVICE int _getNumBlocksRows() const {\n      return (size0 + kRowsPerBlock - 1) / kRowsPerBlock;\n    }\n    CUTLASS_HOST_DEVICE int _getNumBlocksCols() const {\n      return (size1 + kColsPerBlock - 1) / kColsPerBlock;\n    }\n    CUTLASS_HOST_DEVICE int getBlocksGrid() const {\n      return _getNumBlocksRows() * _getNumBlocksCols();\n    }\n    CUTLASS_HOST_DEVICE int getThreadsGrid() const {\n      return kNumThreadsPerBlock;\n    }\n  };\n\n  CUTLASS_DEVICE static void run(Params p) {\n    // Block-level position\n    int block_id = blockIdx.x;\n    int thread_row = (block_id % p._getNumBlocksRows()) * kRowsPerBlock;\n    int thread_col = (block_id / p._getNumBlocksRows()) * kColsPerBlock;\n\n    // Thread-level position\n    int thread_id = threadIdx.x;\n    constexpr int kTilingRows = kRowsPerBlock / kNumRows;\n    thread_row += (thread_id % kTilingRows) * kNumRows;\n    thread_col += (thread_id / kTilingRows) * kNumCols;\n\n    bool enabled = thread_row < p.size0 && thread_col < p.size1;\n    if (!enabled) {\n      return;\n    }\n\n    // We operate on a small 4x4 patch per thread\n    using FragmentRow = cutlass::Array<Element, kNumCols>;\n    cutlass::Array<ElementWithPos, kNumCols * kNumRows> allValues;\n    cutlass::Array<cutlass::uint1b_t, kNumCols * kNumRows> allOutputs;\n    allOutputs.clear();\n    uint8_t numOutputsPerRow[kNumRows] = {0};\n    uint8_t numOutputsPerCol[kNumCols] = {0};\n    uint8_t totalAdded = 0;\n\n    CUTLASS_PRAGMA_UNROLL\n    for (int row = 0; row < kNumRows; ++row) {\n      FragmentRow l = *reinterpret_cast<FragmentRow const*>(\n          p.input + thread_col + (thread_row + row) * p.stride0);\n      CUTLASS_PRAGMA_UNROLL\n      for (int col = 0; col < kNumCols; ++col) {\n        allValues[col + row * kNumCols].e = l[col];\n        allValues[col + row * kNumCols].row = row;\n        allValues[col + row * kNumCols].col = col;\n      }\n    }\n\n    // Sort - ascending order\n    StaticSort<kNumCols * kNumRows> sorter;\n    sorter(allValues);\n\n    // Take all the values we can starting with the largest\n    int i = allValues.size() - 1;\n    CUTLASS_PRAGMA_UNROLL\n    for (; i >= 0; --i) {\n      if (kHasRandom && totalAdded + p.numRandom == kTakeMaximumCount) {\n        break;\n      }\n\n      auto const& v = allValues[i];\n      if (numOutputsPerRow[v.row] == kMaxPerRow ||\n          numOutputsPerCol[v.col] == kMaxPerCol) {\n        continue;\n      }\n      numOutputsPerRow[v.row] += 1;\n      numOutputsPerCol[v.col] += 1;\n      totalAdded += 1;\n      allOutputs[v.col + v.row * kNumCols] = cutlass::uint1b_t(1);\n    }\n\n    // Add random elements now\n    if (kHasRandom) {\n      cutlass::Array<PseudoRandomOrder, kNumCols * kNumRows> randomSorting;\n      CUTLASS_PRAGMA_UNROLL\n      for (int j = 0; j < randomSorting.size(); ++j) {\n        randomSorting[j].row = allValues[j].row;\n        randomSorting[j].col = allValues[j].col;\n        randomSorting[j].random_score = uint16_t(-1);\n        if (j <= i) { // not already considered\n          uint16_t seed = allValues[j].e.storage;\n          // Assume the lower bits of the significant are more random\n          seed = seed & 0x2f;\n          randomSorting[j].random_score = (seed ^ (seed << 1)) & 0x7fff;\n        }\n      }\n      sorter(randomSorting);\n      CUTLASS_PRAGMA_UNROLL\n      for (int j = 0; j < randomSorting.size(); ++j) {\n        auto const& v = randomSorting[j];\n        if (numOutputsPerRow[v.row] == kMaxPerRow ||\n            numOutputsPerCol[v.col] == kMaxPerCol ||\n            allOutputs[v.col + v.row * kNumCols].get()) {\n          continue;\n        }\n        numOutputsPerRow[v.row] += 1;\n        numOutputsPerCol[v.col] += 1;\n        allOutputs[v.col + v.row * kNumCols] = cutlass::uint1b_t(1);\n      }\n    }\n\n    // Write output\n    CUTLASS_PRAGMA_UNROLL\n    for (int row = 0; row < kNumRows; ++row) {\n      FragmentRow fragmentOut;\n      fragmentOut.clear();\n      CUTLASS_PRAGMA_UNROLL\n      for (int col = 0; col < kNumCols; ++col) {\n        fragmentOut[col] =\n            allOutputs[col + row * kNumCols].get() ? Element(1) : Element(0);\n      }\n      *reinterpret_cast<FragmentRow*>(\n          p.output + thread_col + (thread_row + row) * p.stride0) = fragmentOut;\n    }\n  }\n};\n\ntemplate <typename Kernel>\n__global__ void sparse24_largest_mask_2d_cu(typename Kernel::Params p) {\n  Kernel::run(p);\n}\n\ntemplate <bool kHasRandom>\ntorch::stable::Tensor sparse24_largest_with_random_mask_2d_impl(\n    const torch::stable::Tensor input,\n    int64_t numRandom) {\n  STD_TORCH_CHECK(input.is_cuda(), \"must be a CUDA tensor\");\n  STD_TORCH_CHECK(!xf_is_sparse(input), \"must be a dense tensor\");\n  STD_TORCH_CHECK(input.is_contiguous(), \"must be contiguous\");\n  STD_TORCH_CHECK(input.dim() == 2, \"only works on 2d tensors\");\n  STD_TORCH_CHECK(numRandom <= 8, \"There are at most 4x2 elements\")\n\n  torch::stable::accelerator::DeviceGuard device_guard(input.device().index());\n  cudaStream_t stream = xf_getCurrentCUDAStream();\n  torch::stable::Tensor output = torch::stable::empty_like(input);\n\n  auto runKernel = [&](auto _) {\n    using Element = decltype(_);\n    using Kernel = Sp24MaskKernel<Element, kHasRandom>;\n    typename Kernel::Params p;\n    p.input = (Element*)input.data_ptr();\n    p.output = (Element*)output.data_ptr();\n    p.stride0 = input.stride(0);\n    p.size0 = input.size(0);\n    p.size1 = input.size(1);\n    p.numRandom = numRandom;\n    STD_TORCH_CHECK((input.size(-1) % Kernel::kNumRows) == 0, \"Wrong shape\");\n    STD_TORCH_CHECK((input.size(-2) % Kernel::kNumCols) == 0, \"Wrong shape\");\n\n    sparse24_largest_mask_2d_cu<Kernel>\n        <<<p.getBlocksGrid(), p.getThreadsGrid(), 0, stream>>>(p);\n  };\n  if (input.scalar_type() == torch::headeronly::ScalarType::Half) {\n    runKernel(cutlass::half_t(0));\n  } else {\n    STD_TORCH_CHECK(\n        input.scalar_type() == torch::headeronly::ScalarType::BFloat16,\n        \"only f16/bf16 supported\");\n    runKernel(cutlass::bfloat16_t(0));\n  }\n  return output;\n}\n\ntorch::stable::Tensor sparse24_largest_mask_2d(\n    const torch::stable::Tensor input) {\n  return sparse24_largest_with_random_mask_2d_impl<false>(input, 0);\n}\n\ntorch::stable::Tensor sparse24_largest_with_Krandom_mask_2d(\n    const torch::stable::Tensor input,\n    int64_t numRandom) {\n  return sparse24_largest_with_random_mask_2d_impl<true>(input, numRandom);\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\"sparse24_largest_mask_2d\", TORCH_BOX(sparse24_largest_mask_2d));\n  m.impl(\n      \"sparse24_largest_with_Krandom_mask_2d\",\n      TORCH_BOX(sparse24_largest_with_Krandom_mask_2d));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_metadata.h",
    "content": "#pragma once\n#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include \"compute_sparse_tile.h\"\n#include \"pt_stable_utils.h\"\n\nnamespace xformers {\nnamespace sp24 {\ntemplate <typename ElementCutlass>\nstruct CutlassToAt;\n\ntemplate <>\nstruct CutlassToAt<cutlass::half_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::Half;\n};\ntemplate <>\nstruct CutlassToAt<cutlass::bfloat16_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::BFloat16;\n};\ntemplate <>\nstruct CutlassToAt<cutlass::float_e4m3_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::Float8_e4m3fn;\n};\ntemplate <>\nstruct CutlassToAt<uint16_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::UInt16;\n};\ntemplate <>\nstruct CutlassToAt<int32_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::Int;\n};\ntemplate <>\nstruct CutlassToAt<uint8_t> {\n  static auto constexpr value = torch::headeronly::ScalarType::Byte;\n};\ntemplate <>\nstruct CutlassToAt<float> {\n  static auto constexpr value = torch::headeronly::ScalarType::Float;\n};\n\nstruct MetadataCuSparseLtSm80 {\n  // Format used by cuSparseLt\n  // This is based on reverse-engineering, for a visual illustration:\n  // https://docs.google.com/presentation/d/1DtmKThv8S5QAyBktuLRYzZhRzCvS1qSkBbrqNCjMPeA/edit#slide=id.g29afe95bda8_0_0\n  static constexpr int kStrideBlock32x32 =\n      (32 * 32) / (sizeof(ElementInputE) * 8);\n\n  ElementInputE* _meta;\n  ElementInputE* _meta_trans;\n  int64_t _rows;\n  int64_t _cols;\n\n  static int64_t getMetadataSize(int rows, int cols) {\n    STD_TORCH_CHECK(\n        rows % 128 == 0 && cols % 128 == 0,\n        \"Only supports rows/cols multiples of 128\");\n    // 1 bit per dense value\n    return (rows * cols) / (8 * sizeof(ElementInputE));\n  }\n  static std::tuple<\n      torch::stable::Tensor, // return value of the function\n      torch::stable::Tensor, // packed\n      torch::stable::Tensor // packed_meta\n      >\n  create_compressed_representation(\n      int rows,\n      int cols,\n      torch::stable::Tensor const& like,\n      bool needs_metadata) {\n    STD_TORCH_CHECK(\n        like.scalar_type() == torch::headeronly::ScalarType::Half ||\n        like.scalar_type() == torch::headeronly::ScalarType::BFloat16);\n    constexpr int kBytesPerScalar = 2;\n    int64_t data_scalars = rows * cutlass::ceil_div(cols, 2);\n    int64_t meta_scalars = getMetadataSize(rows, cols);\n\n    torch::stable::Tensor storage =\n        torch::stable::new_empty(like, {(data_scalars + meta_scalars)});\n    torch::stable::Tensor packed =\n        xf_slice(storage, 0, std::nullopt, data_scalars);\n    packed = torch::stable::view(packed, {rows, cutlass::ceil_div(cols, 2)});\n    torch::stable::Tensor metadata =\n        xf_slice(storage, 0, data_scalars, std::nullopt);\n    // TODO: Cast metadata to Short\n    static_assert(kBytesPerScalar == 2, \"or modify the last dim below\");\n    metadata = torch::stable::view(metadata, {rows / 128, cols / 32, 256});\n    storage = torch::stable::view(storage, {rows, -1});\n    return std::make_tuple(storage, packed, metadata);\n  }\n  MetadataCuSparseLtSm80(\n      torch::stable::Tensor metaN,\n      torch::stable::Tensor metaT,\n      int rows,\n      int cols) {\n    _meta = (ElementInputE*)metaN.data_ptr();\n    _meta_trans = (ElementInputE*)metaT.data_ptr();\n    _rows = rows;\n    _cols = cols;\n  }\n  CUTLASS_HOST_DEVICE\n  static int64_t _get_meta_offset(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col,\n      int totalRows) {\n    int64_t offset = 0;\n    // warp-level: Find the 128x64 tile\n    offset += (warp_row / 128) * (kStrideBlock32x32 * 8);\n    offset += (warp_col / 64) * (kStrideBlock32x32 * 8) * (totalRows / 128);\n    // Find the 32x32 tile inside\n    offset += (((warp_row + thread_row) % 128) / 32) * kStrideBlock32x32;\n    offset += (((warp_col + thread_col) % 64) / 32) * (kStrideBlock32x32 * 4);\n    // Inside the 32x32 tile\n    offset += (warp_row % 32) * 2;\n    // Top/bottom 16x16 tile\n    offset += ((thread_row % 32) / 16) * 4;\n    // Left/right 16x16 tile\n    offset += ((thread_col % 32) / 16) * 2;\n    return offset;\n  }\n  CUTLASS_HOST_DEVICE\n  ElementInputE* get_metaN(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col) const {\n    return _meta +\n        _get_meta_offset(warp_row, thread_row, warp_col, thread_col, _rows);\n  }\n  CUTLASS_HOST_DEVICE\n  ElementInputE* get_metaT(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col) const {\n    return _meta_trans +\n        _get_meta_offset(warp_col, thread_col, warp_row, thread_row, _cols);\n  }\n};\n\nstruct MetadataCutlassSm80 {\n  // Layout needed to run 2:4 gemms in CUTLASS\n  // There is basically a hardware specific value for every\n  // 32x32 dense tile (1024 bits). Then these tiles are\n  // stored in a Column-Major fashion\n  ElementInputE* _meta;\n  ElementInputE* _meta_trans;\n  int64_t _meta_reordered_sy;\n  int64_t _meta_trans_reordered_sx;\n\n  static std::tuple<\n      torch::stable::Tensor, // return value of the function\n      torch::stable::Tensor, // packed\n      torch::stable::Tensor // packed_meta\n      >\n  create_compressed_representation(\n      int rows,\n      int cols,\n      torch::stable::Tensor const& like,\n      bool needs_metadata) {\n    STD_TORCH_CHECK(\n        like.scalar_type() == torch::headeronly::ScalarType::Half ||\n        like.scalar_type() == torch::headeronly::ScalarType::BFloat16);\n    auto roundedx = cutlass::round_up(rows, kWarpX);\n    auto roundedy = cutlass::round_up(cols, kWarpY);\n\n    // NB: Writing to `packed` tensors in transposed manner\n    torch::stable::Tensor packed = torch::stable::new_empty(\n        like, {roundedx, cutlass::ceil_div(roundedy, 2)});\n    torch::stable::Tensor packed_meta;\n    if (needs_metadata) {\n      packed_meta = torch::stable::new_empty(\n          like,\n          {roundedx * roundedy / 16},\n          torch::headeronly::ScalarType::Short);\n      packed_meta =\n          torch::stable::view(packed_meta, {roundedy / 32, roundedx, 2});\n      packed_meta = xf_permute(packed_meta, {1, 2, 0});\n    }\n    return std::make_tuple(packed, packed, packed_meta);\n  }\n  MetadataCutlassSm80(\n      torch::stable::Tensor metaN,\n      torch::stable::Tensor metaT,\n      int rows,\n      int cols) {\n    _meta = (ElementInputE*)metaN.data_ptr();\n    _meta_reordered_sy = metaN.stride(2);\n    _meta_trans = (ElementInputE*)metaT.data_ptr();\n    _meta_trans_reordered_sx = metaT.stride(2);\n  }\n  CUTLASS_HOST_DEVICE\n  int64_t _get_meta_offset(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col,\n      int64_t stride) const {\n    int64_t offset = 0;\n    offset += warp_row * 2 + (warp_col / 32) * stride;\n    // A single warp is 32x64. The right 32x32 tile is at a different position\n    offset += 64 * (thread_row / 32);\n    offset += (thread_col / 32) * stride;\n    // Top/bottom 16x16 tile\n    offset += ((thread_row % 32) / 16) * 4;\n    // Left/right 16x16 tile\n    offset += ((thread_col % 32) / 16) * 2;\n    return offset;\n  }\n  CUTLASS_HOST_DEVICE\n  ElementInputE* get_metaN(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col) const {\n    return _meta +\n        _get_meta_offset(\n               warp_row, thread_row, warp_col, thread_col, _meta_reordered_sy);\n  }\n  CUTLASS_HOST_DEVICE\n  ElementInputE* get_metaT(\n      int warp_row,\n      int thread_row,\n      int warp_col,\n      int thread_col) const {\n    return _meta_trans +\n        _get_meta_offset(\n               warp_col,\n               thread_col,\n               warp_row,\n               thread_row,\n               _meta_trans_reordered_sx);\n  }\n};\n\nstruct MetadataCutlass8bitsSm90 {\n  template <typename ElementOut>\n  static std::tuple<torch::stable::Tensor, torch::stable::Tensor> createTensors(\n      torch::stable::Tensor input) {\n    auto n_rows = input.size(0);\n    auto n_cols = input.size(1);\n    STD_TORCH_CHECK(n_cols % 128 == 0); // aligned metadata\n    STD_TORCH_CHECK(n_rows % 64 == 0); // aligned metadata\n    int mdata_bytes = n_rows * n_cols / 8;\n\n    torch::stable::Tensor packed = torch::stable::new_empty(\n        input,\n        {n_rows, n_cols / 2},\n        /*dtype=*/CutlassToAt<ElementOut>::value);\n    torch::stable::Tensor mdata = torch::stable::new_empty(\n        input, {mdata_bytes}, torch::headeronly::ScalarType::Byte);\n    return std::make_tuple(packed, mdata);\n  }\n  static CUTLASS_HOST_DEVICE int64_t\n  mdataBlockPtrOffset(int row, int col, int64_t n_rows) {\n    constexpr int kStrideRow = 16;\n    return row * kStrideRow + (col / 128 * n_rows * 16) + (col % 128) / 8;\n  }\n};\n\nstruct MetadataCusparseLt16bitsSm90 {\n  template <typename ElementOut>\n  static std::tuple<torch::stable::Tensor, torch::stable::Tensor> createTensors(\n      torch::stable::Tensor input) {\n    auto n_rows = input.size(0);\n    auto n_cols = input.size(1);\n    int packed_elements = n_rows * n_cols / 2;\n    int mdata_bytes = n_rows * n_cols / 8;\n\n    // We assume 2 bytes per element\n    torch::stable::Tensor sparse_packed = torch::stable::new_empty(\n        input,\n        {int64_t(packed_elements + mdata_bytes / sizeof(ElementOut))},\n        /*dtype=*/CutlassToAt<ElementOut>::value);\n    torch::stable::Tensor metadata =\n        xf_slice(sparse_packed, 0, packed_elements, std::nullopt);\n    metadata = xf_view_dtype(metadata, torch::headeronly::ScalarType::Byte);\n    return std::make_tuple(sparse_packed, metadata);\n  }\n};\n\n} // namespace sp24\n} // namespace xformers\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_pack.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n\n#include \"compute_sparse_tile.h\"\n#include \"pt_stable_utils.h\"\n#include \"sparse24_metadata.h\"\n#include \"sparse24_pack.h\"\n\nusing namespace xformers::sp24;\n\nnamespace {\ntemplate <typename KT, typename Metadata, typename Algorithm>\n__global__ void __launch_bounds__(32 /* num_threads */, 20)\n    sparse24_sparsify_both_ways_kernel(\n        typename KT::Params p,\n        Metadata metadata,\n        Algorithm algo) {\n  KT::sparse24_sparsify_both_ways_kernel(p, metadata, algo);\n}\n\ntemplate <typename Element, typename MetadataFormat, bool kIsMeta>\nstd::\n    tuple<\n        torch::stable::Tensor, // packed\n        torch::stable::Tensor, // packed_meta_reordered\n        torch::stable::Tensor, // packed_trans\n        torch::stable::Tensor, // packed_trans_meta_reordered\n        torch::stable::Tensor // threads_masks\n        >\n    sparse24_sparsify_both_ways_typed(\n        const torch::stable::Tensor input,\n        std::string algorithm) {\n  using KT = KernelTypes<Element>;\n  std::optional<torch::stable::accelerator::DeviceGuard> device_guard;\n  if (!kIsMeta) {\n    device_guard.emplace(input.device().index());\n  }\n\n  STD_TORCH_CHECK(input.dim() == 2, \"Can only sparsify 2d tensors\");\n  STD_TORCH_CHECK(\n      input.stride(1) == 1,\n      \"Can only sparsify contiguous tensors. Sparsify the transpose otherwise.\");\n\n  auto rows = input.size(0);\n  auto cols = input.size(1);\n\n  auto [compressed, packed, packed_meta_reordered] =\n      MetadataFormat::create_compressed_representation(rows, cols, input, true);\n  auto [compressed_trans, packed_trans, packed_trans_meta_reordered] =\n      MetadataFormat::create_compressed_representation(cols, rows, input, true);\n  STD_TORCH_CHECK(\n      input.size(1) % 32 == 0, \"Number of cols should be multiple of 32\");\n\n  typename KT::Params p;\n  p.input_s0 = input.stride(0);\n  p.input_dim0 = input.size(0);\n  p.input_dim1 = input.size(1);\n  p.packed_stride = packed.stride(0);\n  p.packed_trans_stride = packed_trans.stride(0);\n\n  MetadataFormat metadata = MetadataFormat(\n      packed_meta_reordered, packed_trans_meta_reordered, rows, cols);\n  torch::stable::Tensor threads_masks = torch::stable::new_empty(\n      input,\n      {p.getBlocksGrid().x * p.getThreadsGrid().x,\n       p.getBlocksGrid().y * p.getThreadsGrid().y,\n       sizeof(p.threads_masks[0])},\n      torch::headeronly::ScalarType::Byte);\n  if (!kIsMeta) {\n    p.input = (Element const*)input.data_ptr();\n    p.packed = (Element*)packed.data_ptr();\n    p.packed_trans = (Element*)packed_trans.data_ptr();\n    p.threads_masks = (uint64_t*)threads_masks.data_ptr();\n  }\n\n  bool kernel_launched = false;\n  auto launchKernel = [&](auto algo, std::string const& algo_name) {\n    if (algo_name == algorithm) {\n      kernel_launched = true;\n      if (kIsMeta) {\n        return;\n      }\n      size_t smem_bytes = 0;\n      sparse24_sparsify_both_ways_kernel<KT>\n          <<<p.getBlocksGrid(),\n             p.getThreadsGrid(),\n             smem_bytes,\n             xf_getCurrentCUDAStream()>>>(p, metadata, algo);\n    }\n  };\n  named_algorithms(launchKernel);\n  STD_TORCH_CHECK(kernel_launched, \"Unknown algorithm \\\"\", algorithm, \"\\\"\");\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n  return std::make_tuple(\n      compressed,\n      packed_meta_reordered,\n      compressed_trans,\n      packed_trans_meta_reordered,\n      threads_masks);\n}\n\ntemplate <bool kIsMeta = false>\nstd::\n    tuple<\n        torch::stable::Tensor, // packed\n        torch::stable::Tensor, // packed_meta_reordered\n        torch::stable::Tensor, // packed_trans\n        torch::stable::Tensor, // packed_trans_meta_reordered\n        torch::stable::Tensor // threads_masks\n        >\n    sparse24_sparsify_both_ways(\n        const torch::stable::Tensor input,\n        std::string algorithm,\n        std::string backend) {\n  auto runTyped = [&](auto type) {\n    using ElementT = decltype(type);\n    if (backend == \"cusparselt\") {\n      return sparse24_sparsify_both_ways_typed<\n          ElementT,\n          MetadataCuSparseLtSm80,\n          kIsMeta>(input, algorithm);\n    } else {\n      STD_TORCH_CHECK(\n          backend == \"cutlass\",\n          \"backend argument only supports `cutlass` or `cusparselt`\");\n      return sparse24_sparsify_both_ways_typed<\n          ElementT,\n          MetadataCutlassSm80,\n          kIsMeta>(input, algorithm);\n    }\n  };\n\n  if (input.scalar_type() == torch::headeronly::ScalarType::Half) {\n    return runTyped(cutlass::half_t());\n  } else {\n    STD_TORCH_CHECK(\n        input.scalar_type() == torch::headeronly::ScalarType::Half ||\n        input.scalar_type() == torch::headeronly::ScalarType::BFloat16);\n    return runTyped(cutlass::bfloat16_t());\n  }\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\n      \"sparse24_sparsify_both_ways\",\n      TORCH_BOX(sparse24_sparsify_both_ways<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\n      \"sparse24_sparsify_both_ways\",\n      TORCH_BOX(sparse24_sparsify_both_ways<true>));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_pack.h",
    "content": "#pragma once\n\n#include <cutlass/arch/memory.h>\n#include <cutlass/array.h>\n#include <cutlass/bfloat16.h>\n#include <cutlass/fast_math.h>\n#include <cutlass/half.h>\n#include <cutlass/integer_subbyte.h>\n#include \"static_sort.h\"\n\nnamespace xformers {\nnamespace sp24 {\nusing cutlass::uint1b_t;\nusing cutlass::uint2b_t;\nusing cutlass::uint4b_t;\nusing uint8b_t = cutlass::integer_subbyte<8, false>;\nusing ReorderedLayoutInputE = cutlass::layout::ColumnMajorInterleaved<2>;\nusing ElementInputE = uint16_t;\nconstexpr int kWarpX = 32;\nconstexpr int kWarpY = 64;\nconstexpr int kThreadX = 8;\nconstexpr int kThreadY = 8;\n\n// bitmask of selected values, in col-major storage\n// eg: indices & (1 << (col + 4 * row))\nusing Indices4x4 = uint16_t;\n\nstruct Tile8x8Masks {\n  Indices4x4 a, b, c, d;\n  CUTLASS_DEVICE Tile8x8Masks() {\n    a = b = c = d = 0;\n  }\n};\n\nstatic_assert(sizeof(Tile8x8Masks) == 8, \"should be exactly uint64_t\");\n\n// Each thread has data for an 8x8 area of the input tensor\n// Due to the very specific format of the metadata, 32 consecutive bits\n// of the metadata tensor will live in 4 different threads.\n// This functions does the required warp shuffling to send data to the\n// right threads.\n// This took some time to write (and get right), hopefully these slides\n// can help\n// https://docs.google.com/presentation/d/1DtmKThv8S5QAyBktuLRYzZhRzCvS1qSkBbrqNCjMPeA/edit#slide=id.g249eb2e2f2e_0_28\nCUTLASS_DEVICE uint32_t\nwarp_shuffle_meta(uint32_t meta_ab, bool transposed = false) {\n  // The required format is\n  // (one line = 32 bits)\n  // a[ 0,  0:16] a[ 8,  0:16] <- T0 [left]\n  // a[ 0, 16:32] a[ 8, 16:32]\n  // a[16,  0:16] a[24,  0:16]\n  // a[16, 16:32] a[24, 16:32]\n  // a[ 1,  0:16] a[ 9,  0:16] <- T4\n  // a[ 1, 16:32] a[ 9, 16:32]\n  // a[17,  0:16] a[25,  0:16]\n  // a[17, 16:32] a[25, 16:32]\n  // a[ 2,  0:16] a[10,  0:16] <- T1 [left, bottom]\n  // a[ 2, 16:32] a[10, 16:32]\n  // a[18,  0:16] a[26,  0:16]\n  // a[18, 16:32] a[26, 16:32]\n  // a[ 3,  0:16] a[11,  0:16] <- T5 [bottom]\n  // a[ 3, 16:32] a[11, 16:32]\n  // a[19,  0:16] a[27,  0:16]\n  // a[19, 16:32] a[27, 16:32]\n  // ...\n  // Use warp-shuffles to send data around threads\n  bool thread_left = (threadIdx.y % 2) == 0;\n  bool thread_bottom = threadIdx.x % 2;\n\n  if (transposed) {\n    thread_left = (threadIdx.x % 2) == 0;\n    thread_bottom = threadIdx.y % 2;\n  }\n\n  uint8b_t stage0_data[2] = {\n      uint8b_t(meta_ab >> (8 * thread_left)),\n      uint8b_t(meta_ab >> (8 * (thread_left + 2)))};\n  // shfl t0-t4 / t1-t5\n  stage0_data[0] =\n      uint8b_t{__shfl_xor_sync(0xffffffff, stage0_data[0], transposed ? 1 : 4)};\n  stage0_data[1] =\n      uint8b_t{__shfl_xor_sync(0xffffffff, stage0_data[1], transposed ? 1 : 4)};\n\n  uint16_t line0 = int(uint8b_t(meta_ab >> (8 * (1 - thread_left))))\n      << ((1 - thread_left) * 8);\n  line0 |= int(stage0_data[0]) << (thread_left * 8);\n  uint16_t line1 = int(uint8b_t(meta_ab >> (8 * (1 - thread_left + 2))))\n      << ((1 - thread_left) * 8);\n  line1 |= int(stage0_data[1]) << (thread_left * 8);\n\n  uint16_t stage1_data = thread_bottom ? line0 : line1;\n  stage1_data = __shfl_xor_sync(0xffffffff, stage1_data, transposed ? 4 : 1);\n\n  uint32_t final_metadata;\n  if (thread_bottom) {\n    final_metadata = uint32_t(stage1_data) | uint32_t(line1) << 16;\n  } else {\n    final_metadata = uint32_t(stage1_data) << 16 | uint32_t(line0);\n  }\n  return final_metadata;\n}\n\nCUTLASS_DEVICE void warp_shuffle_and_write_meta(\n    ElementInputE* metadata_quad,\n    uint32_t meta_ab,\n    bool transposed = false) {\n  bool thread_left = (threadIdx.y % 2) == 0;\n  bool thread_bottom = threadIdx.x % 2;\n\n  if (transposed) {\n    thread_left = (threadIdx.x % 2) == 0;\n    thread_bottom = threadIdx.y % 2;\n  }\n\n  uint32_t final_metadata = warp_shuffle_meta(meta_ab, transposed);\n\n  int index = (!thread_left + 2 * thread_bottom) * 4;\n  ((uint32_t*)metadata_quad)[index] = final_metadata;\n}\n\ntemplate <typename Element_>\nstruct KernelTypes {\n  using Element = Element_;\n  using Fragment =\n      cutlass::Array<Element, 8>; // always read from gmem in chunks of 128bits\n  using Fragment4 = cutlass::Array<Element, 4>;\n  using ValuesPacked = cutlass::Array<Element, 8>; // 4 first col, 4 second col\n\n  struct Params {\n    /// inputs\n    Element const* input;\n    int64_t input_s0;\n    int64_t input_dim0;\n    int64_t input_dim1;\n\n    /// outputs\n    Element* packed;\n    int64_t packed_stride;\n\n    Element* packed_trans;\n    int64_t packed_trans_stride;\n\n    uint64_t* threads_masks;\n\n    __host__ dim3 getBlocksGrid() const {\n      return dim3(\n          cutlass::ceil_div(input_dim0, kWarpX),\n          cutlass::ceil_div(input_dim1, kWarpY),\n          1);\n    }\n\n    static CUTLASS_HOST_DEVICE dim3 getThreadsGrid() {\n      return dim3(kWarpX / kThreadX, kWarpY / kThreadY, 1);\n    }\n\n    CUTLASS_DEVICE Tile8x8Masks* getCurrentThreadIndices() const {\n      Tile8x8Masks* gmem_threads_masks = (Tile8x8Masks*)threads_masks;\n      gmem_threads_masks += blockIdx.y * getThreadsGrid().y + threadIdx.y;\n      int64_t strideX = gridDim.y * getThreadsGrid().y;\n      gmem_threads_masks +=\n          (blockIdx.x * getThreadsGrid().x + threadIdx.x) * strideX;\n      return gmem_threads_masks;\n    }\n  };\n\n  struct Tile4x4Accessor {\n    using Element = Element_;\n\n    Fragment (&_lines)[8];\n    int _start_row;\n    int _start_col;\n\n    CUTLASS_DEVICE Tile4x4Accessor(\n        Fragment (&lines)[8],\n        int start_row,\n        int start_col)\n        : _lines(lines), _start_row(start_row), _start_col(start_col) {}\n\n    CUTLASS_DEVICE typename Fragment::reference at(int r, int c) {\n      return _lines[r + _start_row][c + _start_col];\n    }\n  };\n\n  struct Tile4x4Packed {\n    Fragment4 values[2];\n    CUTLASS_DEVICE Tile4x4Packed() {\n      values[0].clear();\n      values[1].clear();\n    }\n  };\n\n  // Returns a packed 4x4 tile (eg 2x4 values) which correspond to the values\n  // that are in `indices`. Also fills the `meta` array in the right format\n  // for consumption in the TensorCores.\n  // Example:\n  //  indices:  0011\n  //            1001\n  //            1001\n  //            0100 (<- note, only 1 value on the last line)\n  //  packed: values[0][2] values[1][0] values[2][0] values[3][1]\n  //          values[0][3] values[1][3] values[2][3] Element(0)\n  CUTLASS_DEVICE static Tile4x4Packed pack_4x4(\n      Indices4x4 indices,\n      Tile4x4Accessor tile,\n      uint32_t& meta,\n      int meta_pos,\n      bool transpose = false) {\n    Tile4x4Packed packed;\n    CUTLASS_PRAGMA_UNROLL\n    for (int row = 0; row < 4; ++row) {\n      uint2b_t col0_from, col1_from;\n      auto packValue = [&](uint2b_t col_to, uint2b_t col_from) {\n        auto value = transpose ? tile.at(col_from, row).get()\n                               : tile.at(row, col_from).get();\n        packed.values[col_to][row] = value;\n        if (col_to == uint2b_t(0)) {\n          col0_from = col_from;\n        } else {\n          col1_from = col_from;\n        }\n      };\n      auto isSelected = [&](int col) {\n        if (transpose) {\n          return indices & (1 << (row + 4 * col));\n        }\n        return indices & (1 << (col + 4 * row));\n      };\n      // Process cols 0/1\n      // We know that col0 is always packed to position 0 if it's there\n      // and col1 is packed to pos 0 or 1 (depending if col0 is selected)\n      if (isSelected(1)) {\n        packValue(uint2b_t{0}, uint2b_t{1});\n      }\n      if (isSelected(0)) {\n        packValue(uint2b_t{0}, uint2b_t{0});\n      }\n      if (isSelected(0) && isSelected(1)) {\n        packValue(uint2b_t{1}, uint2b_t{1});\n      }\n      // Process cols 2/3\n      // same sort of heuristic\n      if (isSelected(2)) {\n        packValue(uint2b_t{1}, uint2b_t{2});\n      }\n      if (isSelected(3)) {\n        packValue(uint2b_t{1}, uint2b_t{3});\n      }\n      if (isSelected(2) && isSelected(3)) {\n        packValue(uint2b_t{0}, uint2b_t{2});\n      }\n      int add_mask = (col0_from | (col1_from << 2)) << (8 * row + meta_pos);\n      meta |= add_mask;\n    }\n    return packed;\n  }\n\n  struct Tile8x8Meta {\n    // meta_ab[row] |= (real_col << (8*row + 2*pos))\n    uint32_t meta_ab;\n    uint32_t meta_cd;\n\n    // meta_ac_trans[col] |= (real_row << (8*col + 2*pos))\n    uint32_t meta_ac_trans;\n    uint32_t meta_bd_trans;\n\n    CUTLASS_DEVICE Tile8x8Meta() {\n      meta_ab = meta_cd = meta_ac_trans = meta_bd_trans = 0;\n    }\n  };\n\n  CUTLASS_DEVICE static void writePacked(\n      Element* ptr,\n      Fragment4 packed0,\n      Fragment4 packed1) {\n    Fragment write;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 4; ++i) {\n      write[i] = packed0[i].get();\n      write[i + 4] = packed1[i].get();\n    }\n    cutlass::arch::global_store<Fragment, sizeof(Fragment)>(write, ptr, true);\n  }\n\n  CUTLASS_DEVICE static void writePackedT(\n      Element* ptr,\n      int64_t stride,\n      Tile4x4Packed a,\n      Tile4x4Packed b) {\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 4; ++i) {\n      Fragment4 write;\n      write[0] = a.values[0][i].get();\n      write[1] = a.values[1][i].get();\n      write[2] = b.values[0][i].get();\n      write[3] = b.values[1][i].get();\n      cutlass::arch::global_store<Fragment4, sizeof(Fragment4)>(\n          write, ptr + i * stride, true);\n    }\n  }\n\n  template <typename Algorithm, typename MetadataStore>\n  CUTLASS_DEVICE static void sparse24_sparsify_both_ways_kernel(\n      Params p,\n      MetadataStore metadata_gmem,\n      Algorithm compute_tile_indices) {\n    // Each thread is responsible for an 8x8 tile, which contains 4 4x4 tiles:\n    // A, B, C and D, as displayed in the following schema:\n    // +---+---+\n    // | A | B |\n    // +---+---+\n    // | C | D |\n    // +---+---+\n    // Each warp (32 threads) will then be responsible for a 32x64 tile of the\n    // input.\n    // This configuration allows to read/write data in 128bits chunks. These\n    // memory accesses are coalesced at the warp-level into 128bytes. See also:\n    // https://docs.google.com/presentation/d/1DtmKThv8S5QAyBktuLRYzZhRzCvS1qSkBbrqNCjMPeA/edit#slide=id.g2494f30c7cf_0_0\n\n    // Top-left of the 8x8 tile we own\n    int warp_x = blockIdx.x * kWarpX;\n    int warp_y = blockIdx.y * kWarpY;\n    int x = warp_x + threadIdx.x * kThreadX;\n    int y = warp_y + threadIdx.y * kThreadY;\n\n    Element const* input = p.input + x * p.input_s0 + y;\n    Element* packed = p.packed + x * p.packed_stride + (y / 2);\n    Element* packed_trans =\n        p.packed_trans + (x / 2) + y * p.packed_trans_stride;\n\n    Fragment lines[8]; // Contains all values from the 8x8 tile\n\n    Tile8x8Meta metadata;\n    Tile8x8Masks indices;\n\n    // Load/process tiles `A` and `B`\n    Element fillValue = Algorithm::template outOfBoundsFillValue<Element>();\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 4; ++i) {\n      lines[i].fill(fillValue);\n      cutlass::arch::global_load<Fragment, sizeof(Fragment)>(\n          lines[i], input + i * p.input_s0, x + i < p.input_dim0);\n    }\n    indices.a = compute_tile_indices(Tile4x4Accessor(lines, 0, 0));\n    indices.b = compute_tile_indices(Tile4x4Accessor(lines, 0, 4));\n\n    // Compute packed tiles A & B\n    {\n      Tile4x4Packed packed_a = pack_4x4(\n          indices.a, Tile4x4Accessor(lines, 0, 0), metadata.meta_ab, 0);\n      Tile4x4Packed packed_b = pack_4x4(\n          indices.b, Tile4x4Accessor(lines, 0, 4), metadata.meta_ab, 4);\n      writePackedT(packed, p.packed_stride, packed_a, packed_b);\n    }\n\n    // Compute/store packed tiles A & B in transpose output\n    Tile4x4Packed packed_trans_a = pack_4x4(\n        indices.a,\n        Tile4x4Accessor(lines, 0, 0),\n        metadata.meta_ac_trans,\n        0,\n        true);\n    Tile4x4Packed packed_trans_b = pack_4x4(\n        indices.b,\n        Tile4x4Accessor(lines, 0, 4),\n        metadata.meta_bd_trans,\n        0,\n        true);\n    // (NOTE) Now we no longer need A & B (`lines[0:4]`)\n\n    // Load/process tiles `C` and `D`\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 4; i < 8; ++i) {\n      lines[i].fill(fillValue);\n      cutlass::arch::global_load<Fragment, sizeof(Fragment)>(\n          lines[i], input + i * p.input_s0, x + i < p.input_dim0);\n    }\n    indices.c = compute_tile_indices(Tile4x4Accessor(lines, 4, 0));\n    indices.d = compute_tile_indices(Tile4x4Accessor(lines, 4, 4));\n\n    // Compute packed tiles C & D\n    {\n      Tile4x4Packed packed_c = pack_4x4(\n          indices.c, Tile4x4Accessor(lines, 4, 0), metadata.meta_cd, 0);\n      Tile4x4Packed packed_d = pack_4x4(\n          indices.d, Tile4x4Accessor(lines, 4, 4), metadata.meta_cd, 4);\n      writePackedT(\n          packed + 4 * p.packed_stride, p.packed_stride, packed_c, packed_d);\n    }\n\n    // Compute/store packed tiles C & D in transpose output\n    Tile4x4Packed packed_trans_c = pack_4x4(\n        indices.c,\n        Tile4x4Accessor(lines, 4, 0),\n        metadata.meta_ac_trans,\n        4,\n        true);\n    Tile4x4Packed packed_trans_d = pack_4x4(\n        indices.d,\n        Tile4x4Accessor(lines, 4, 4),\n        metadata.meta_bd_trans,\n        4,\n        true);\n\n    // Dump the metadata in a nice format\n    *p.getCurrentThreadIndices() = indices;\n\n    // Store packed A, B, C & D for transposed matrix\n    writePackedT(\n        packed_trans, p.packed_trans_stride, packed_trans_a, packed_trans_c);\n    packed_trans += 4 * p.packed_trans_stride;\n    writePackedT(\n        packed_trans, p.packed_trans_stride, packed_trans_b, packed_trans_d);\n\n    // Writing meta non-transposed\n    {\n      ElementInputE* packed_meta_reordered = metadata_gmem.get_metaN(\n          warp_x, threadIdx.x * kThreadX, warp_y, threadIdx.y * kThreadY);\n      warp_shuffle_and_write_meta(packed_meta_reordered, metadata.meta_ab);\n      warp_shuffle_and_write_meta(packed_meta_reordered + 32, metadata.meta_cd);\n    }\n\n    // Writing meta transposed\n    {\n      ElementInputE* packed_trans_meta_reordered = metadata_gmem.get_metaT(\n          warp_x, threadIdx.x * kThreadX, warp_y, threadIdx.y * kThreadY);\n      warp_shuffle_and_write_meta(\n          packed_trans_meta_reordered, metadata.meta_ac_trans, true);\n      warp_shuffle_and_write_meta(\n          packed_trans_meta_reordered + 32, metadata.meta_bd_trans, true);\n    }\n  }\n\n  CUTLASS_DEVICE static void sparse24_apply_kernel(Params p) {\n    // See `sparse24_sparsify_both_ways_kernel`\n    // It's basically the same, just that we skip\n    // the part where compute the indices we keep\n\n    // Top-left of the 8x8 tile we own\n    int warp_x = blockIdx.x * kWarpX;\n    int warp_y = blockIdx.y * kWarpY;\n    int x = warp_x + threadIdx.x * kThreadX;\n    int y = warp_y + threadIdx.y * kThreadY;\n\n    Element const* input = p.input + x * p.input_s0 + y;\n    Element* packed = p.packed + x * p.packed_stride + (y / 2);\n    Element* packed_trans =\n        p.packed_trans + (x / 2) + y * p.packed_trans_stride;\n\n    Fragment lines[8]; // Contains all values from the 8x8 tile\n\n    Tile8x8Meta metadata;\n    Tile8x8Masks indices = *p.getCurrentThreadIndices();\n\n    // Load/process tiles `A` and `B`\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < 8; ++i) {\n      // NB: Values outside bounds is undefined, but shouldn't\n      // be used anywhere\n      cutlass::arch::global_load<Fragment, sizeof(Fragment)>(\n          lines[i], input + i * p.input_s0, x + i < p.input_dim0);\n    }\n\n    // Compute packed tiles A & B\n    {\n      Tile4x4Packed packed_a = pack_4x4(\n          indices.a, Tile4x4Accessor(lines, 0, 0), metadata.meta_ab, 0);\n      Tile4x4Packed packed_b = pack_4x4(\n          indices.b, Tile4x4Accessor(lines, 0, 4), metadata.meta_ab, 4);\n      writePackedT(packed, p.packed_stride, packed_a, packed_b);\n    }\n\n    // Compute/store packed tiles A & B in transpose output\n    Tile4x4Packed packed_trans_a = pack_4x4(\n        indices.a,\n        Tile4x4Accessor(lines, 0, 0),\n        metadata.meta_ac_trans,\n        0,\n        true);\n    Tile4x4Packed packed_trans_b = pack_4x4(\n        indices.b,\n        Tile4x4Accessor(lines, 0, 4),\n        metadata.meta_bd_trans,\n        0,\n        true);\n    // (NOTE) Now we no longer need A & B (`lines[0:4]`)\n\n    // Compute packed tiles C & D\n    {\n      Tile4x4Packed packed_c = pack_4x4(\n          indices.c, Tile4x4Accessor(lines, 4, 0), metadata.meta_cd, 0);\n      Tile4x4Packed packed_d = pack_4x4(\n          indices.d, Tile4x4Accessor(lines, 4, 4), metadata.meta_cd, 4);\n      writePackedT(\n          packed + 4 * p.packed_stride, p.packed_stride, packed_c, packed_d);\n    }\n\n    // Compute/store packed tiles C & D in transpose output\n    Tile4x4Packed packed_trans_c = pack_4x4(\n        indices.c,\n        Tile4x4Accessor(lines, 4, 0),\n        metadata.meta_ac_trans,\n        4,\n        true);\n    Tile4x4Packed packed_trans_d = pack_4x4(\n        indices.d,\n        Tile4x4Accessor(lines, 4, 4),\n        metadata.meta_bd_trans,\n        4,\n        true);\n\n    // Store packed A, B, C & D for transposed matrix\n    writePackedT(\n        packed_trans, p.packed_trans_stride, packed_trans_a, packed_trans_c);\n    packed_trans += 4 * p.packed_trans_stride;\n    writePackedT(\n        packed_trans, p.packed_trans_stride, packed_trans_b, packed_trans_d);\n  }\n};\n} // namespace sp24\n} // namespace xformers\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparse24_pack_test.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/TensorAccessor.h>\n\n#include \"pt_stable_utils.h\"\n#include \"sparse24_pack.h\"\n\nusing namespace xformers::sp24;\n\nnamespace {\n__global__ void meta_shuffle_test_kernel(\n    torch::headeronly::HeaderOnlyGenericPackedTensorAccessor<int64_t, 3>\n        local_meta,\n    torch::headeronly::HeaderOnlyGenericPackedTensorAccessor<int64_t, 3>\n        final_meta,\n    bool transpose) {\n  uint32_t meta_ab = 0;\n  uint32_t meta_cd = 0;\n  for (int i = 0; i < 4; ++i) {\n    meta_ab |= uint8b_t(uint32_t(local_meta[threadIdx.x][threadIdx.y][i]))\n        << (8 * i);\n    meta_cd |= uint8b_t(uint32_t(local_meta[threadIdx.x][threadIdx.y][4 + i]))\n        << (8 * i);\n  }\n  final_meta[threadIdx.x][threadIdx.y][0] =\n      warp_shuffle_meta(meta_ab, transpose);\n  final_meta[threadIdx.x][threadIdx.y][1] =\n      warp_shuffle_meta(meta_cd, transpose);\n}\n\ntorch::stable::Tensor _sparse24_meta_shuffle_test(\n    torch::stable::Tensor local_meta,\n    bool transpose) {\n  auto threads_grid = KernelTypes<cutlass::half_t>::Params::getThreadsGrid();\n\n  STD_TORCH_CHECK(\n      local_meta.scalar_type() == torch::headeronly::ScalarType::Long);\n  STD_TORCH_CHECK(local_meta.dim() == 3);\n  STD_TORCH_CHECK(local_meta.size(0) == threads_grid.x);\n  STD_TORCH_CHECK(local_meta.size(1) == threads_grid.y);\n  STD_TORCH_CHECK(local_meta.size(2) == kThreadY);\n  torch::stable::accelerator::DeviceGuard device_guard(\n      local_meta.device().index());\n  cudaStream_t stream = xf_getCurrentCUDAStream();\n\n  torch::stable::Tensor final_meta = xf_zeros(\n      {threads_grid.x, threads_grid.y, 2},\n      local_meta.scalar_type(),\n      local_meta.device());\n  size_t smem_bytes = 0;\n  meta_shuffle_test_kernel<<<1, threads_grid, smem_bytes, stream>>>(\n      xf_packed_accessor<int64_t, 3>(local_meta),\n      xf_packed_accessor<int64_t, 3>(final_meta),\n      transpose);\n  return final_meta;\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\"_sparse24_meta_shuffle_test\", TORCH_BOX(_sparse24_meta_shuffle_test));\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/sparseNM_dense.cu",
    "content": "#include <torch/csrc/stable/accelerator.h>\n#include <torch/csrc/stable/device.h>\n#include <torch/csrc/stable/library.h>\n#include <torch/csrc/stable/macros.h>\n#include <torch/csrc/stable/ops.h>\n#include <torch/csrc/stable/tensor.h>\n#include <torch/headeronly/core/ScalarType.h>\n\n#include \"pt_stable_utils.h\"\n#include \"warp_tensor.h\"\n\nusing namespace xformers::sp24;\n\nnamespace {\ntemplate <int N, int M, typename Element, typename PreprocFn>\nvoid __global__ sparseNM_dense_kernel(\n    Element const* in_ptr,\n    int64_t in_s0,\n    Element* out_ptr,\n    int64_t out_s0,\n    PreprocFn sort_preproc) {\n  constexpr int kBlockSize0 = 64;\n  constexpr int kBlockSize1 = 64;\n\n  int64_t block0 = blockIdx.x * kBlockSize0;\n  int64_t block1 = blockIdx.y * kBlockSize1;\n\n  WarpTensor<Element, 8, M * 4> tile;\n  static_assert(tile.kElementsPerThread == M);\n  CUTLASS_PRAGMA_UNROLL\n  for (int tile0 = 0; tile0 < kBlockSize0; tile0 += tile.kRows) {\n    CUTLASS_PRAGMA_UNROLL\n    for (int tile1 = 0; tile1 < kBlockSize1; tile1 += tile.kCols) {\n      // load\n      tile.load(in_ptr + (block0 + tile0) * in_s0 + block1 + tile1, in_s0);\n\n      // sparsify dense\n      tile = tile.template sparsify_dense<N, M>(sort_preproc);\n\n      // store\n      tile.store(out_ptr + (block0 + tile0) * out_s0 + block1 + tile1, out_s0);\n    }\n  }\n}\n\ntemplate <bool kIsMeta>\ntorch::stable::Tensor sparseNM_dense(\n    torch::stable::Tensor input,\n    std::string sort_preproc,\n    int64_t kN,\n    int64_t kM) {\n  std::optional<torch::stable::accelerator::DeviceGuard> device_guard;\n  if (!kIsMeta) {\n    STD_TORCH_CHECK(input.is_cuda(), \"All tensors must be on GPU\");\n    device_guard.emplace(input.device().index());\n  }\n\n  STD_TORCH_CHECK(input.dim() == 2, \"Can only sparsify 2d tensors\");\n  STD_TORCH_CHECK(\n      input.stride(1) == 1,\n      \"Can only sparsify contiguous tensors. Sparsify the transpose otherwise.\");\n  STD_TORCH_CHECK(input.size(0) % 64 == 0);\n  STD_TORCH_CHECK(input.size(1) % 64 == 0);\n\n  torch::stable::Tensor out = torch::stable::empty_like(input);\n  bool foundKernel = false;\n  auto launchKernel = [&](auto dtype, auto N, auto M, auto sort_preproc) {\n    if (foundKernel) {\n      return;\n    }\n    foundKernel = true;\n    if (kIsMeta) {\n      return;\n    }\n    using Element = decltype(dtype);\n    dim3 num_blocks(input.size(0) / 64, input.size(1) / 64, 1);\n    sparseNM_dense_kernel<N.value, M.value>\n        <<<num_blocks, 32, 0, xf_getCurrentCUDAStream()>>>(\n            (Element const*)input.data_ptr(),\n            input.stride(0),\n            (Element*)out.data_ptr(),\n            out.stride(0),\n            sort_preproc);\n  };\n  STD_TORCH_CHECK(\n      input.scalar_type() == torch::headeronly::ScalarType::BFloat16);\n  auto dtype = cutlass::bfloat16_t();\n  auto Identity = [] __device__(auto x) { return x; };\n  auto Abs = [] __device__(auto x) { return cutlass::abs(x); };\n  auto testNM = [&](auto N, auto M) {\n    if (N.value != kN || M.value != kM) {\n      return;\n    }\n    if (sort_preproc == \"largest\") {\n      launchKernel(dtype, N, M, Identity);\n    } else if (sort_preproc == \"largest_abs\") {\n      launchKernel(dtype, N, M, Abs);\n    }\n  };\n  // 2:8 sparsification\n  testNM(std::integral_constant<int, 2>(), std::integral_constant<int, 8>());\n  // 2:4 sparsification\n  testNM(std::integral_constant<int, 2>(), std::integral_constant<int, 4>());\n  STD_TORCH_CHECK(foundKernel, \"Kernel not found\");\n  STD_CUDA_KERNEL_LAUNCH_CHECK();\n\n  return out;\n}\n} // namespace\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, CUDA, m) {\n  m.impl(\"sparseNM_dense\", TORCH_BOX(sparseNM_dense<false>));\n}\n\nSTABLE_TORCH_LIBRARY_IMPL(xformers, Meta, m) {\n  m.impl(\"sparseNM_dense\", TORCH_BOX(sparseNM_dense<true>));\n}\n\nSTABLE_TORCH_LIBRARY_FRAGMENT(xformers, m) {\n  m.def(\n      \"sparseNM_dense(Tensor input, str sort_preproc, int N, int M) -> Tensor\");\n}\n"
  },
  {
    "path": "xformers/csrc/sparse24/static_sort.h",
    "content": "#pragma once\n#include <cutlass/cutlass.h>\n\n/**\n * A Functor class to create a sort for fixed sized arrays/containers with a\n * compile time generated Bose-Nelson sorting network.\n * \\tparam NumElements  The number of elements in the array or container to\n * sort. \\tparam T            The element type. \\tparam Compare      A\n * comparator functor class that returns true if lhs < rhs.\n */\ntemplate <unsigned NumElements>\nclass StaticSort {\n  template <class A>\n  struct Swap {\n    template <class T>\n    CUTLASS_HOST_DEVICE void s(T& v0, T& v1) {\n      // Explicitly code out the Min and Max to nudge the compiler\n      // to generate branchless code.\n      T t = v0 < v1 ? v0 : v1; // Min\n      v1 = v0 < v1 ? v1 : v0; // Max\n      v0 = t;\n    }\n\n    CUTLASS_HOST_DEVICE Swap(A& a, const int& i0, const int& i1) {\n      s(a[i0], a[i1]);\n    }\n  };\n\n  template <class A, int I, int J, int X, int Y>\n  struct PB {\n    CUTLASS_HOST_DEVICE PB(A& a) {\n      enum {\n        L = X >> 1,\n        M = (X & 1 ? Y : Y + 1) >> 1,\n        IAddL = I + L,\n        XSubL = X - L\n      };\n      PB<A, I, J, L, M> p0(a);\n      PB<A, IAddL, J + M, XSubL, Y - M> p1(a);\n      PB<A, IAddL, J, XSubL, M> p2(a);\n    }\n  };\n\n  template <class A, int I, int J>\n  struct PB<A, I, J, 1, 1> {\n    CUTLASS_HOST_DEVICE PB(A& a) {\n      Swap<A> s(a, I - 1, J - 1);\n    }\n  };\n\n  template <class A, int I, int J>\n  struct PB<A, I, J, 1, 2> {\n    CUTLASS_HOST_DEVICE PB(A& a) {\n      Swap<A> s0(a, I - 1, J);\n      Swap<A> s1(a, I - 1, J - 1);\n    }\n  };\n\n  template <class A, int I, int J>\n  struct PB<A, I, J, 2, 1> {\n    CUTLASS_HOST_DEVICE PB(A& a) {\n      Swap<A> s0(a, I - 1, J - 1);\n      Swap<A> s1(a, I, J - 1);\n    }\n  };\n\n  template <class A, int I, int M, bool Stop = false>\n  struct PS {\n    CUTLASS_HOST_DEVICE PS(A& a) {\n      enum { L = M >> 1, IAddL = I + L, MSubL = M - L };\n      PS<A, I, L, (L <= 1)> ps0(a);\n      PS<A, IAddL, MSubL, (MSubL <= 1)> ps1(a);\n      PB<A, I, IAddL, L, MSubL> pb(a);\n    }\n  };\n\n  template <class A, int I, int M>\n  struct PS<A, I, M, true> {\n    CUTLASS_HOST_DEVICE PS(A& a) {}\n  };\n\n public:\n  /**\n   * Sorts the array/container arr.\n   * \\param  arr  The array/container to be sorted.\n   */\n  template <class Container>\n  CUTLASS_HOST_DEVICE void operator()(Container& arr) const {\n    PS<Container, 1, NumElements, (NumElements <= 1)> ps(arr);\n  };\n\n  /**\n   * Sorts the array arr.\n   * \\param  arr  The array to be sorted.\n   */\n  template <class T>\n  CUTLASS_HOST_DEVICE void operator()(T* arr) const {\n    PS<T*, 1, NumElements, (NumElements <= 1)> ps(arr);\n  };\n};\n"
  },
  {
    "path": "xformers/csrc/sparse24/warp_tensor.h",
    "content": "#pragma once\n\n#include <cutlass/arch/memory.h>\n#include <cutlass/array.h>\n#include <cutlass/numeric_conversion.h>\n\n#include \"static_sort.h\"\n\nnamespace xformers {\nnamespace sp24 {\n\ntemplate <typename Element, int kRows_, int kCols_>\nstruct WarpTensor {\n  // This class represents a Tensor sharded across an entire warp,\n  // on registers. The sharding is row-major, eg looks like this\n  // for a `WarpTensor<T, 8, 32>`:\n  // [row 0]   [thread0][thread1]...\n  // [row 1]   [thread4][thread5]...\n  //...\n  // [row 8]   [thread28][thread29]...\n  // Each thread would hold 8 values. This format is optimized to\n  // load from gmem efficiently (coalescing)\n\n  static constexpr int kRows = kRows_;\n  static constexpr int kCols = kCols_;\n  // NOTE: Stored in Row-Major\n  static constexpr int kElementsPerThread = (kRows * kCols / 32);\n  static constexpr int kThreadsPerRow = 32 / kRows;\n  static_assert(32 % kRows == 0);\n\n  cutlass::Array<Element, kElementsPerThread> data; // < current thread data\n  int lane = threadIdx.x % 32;\n\n  CUTLASS_DEVICE int thread_row() const {\n    return lane / kThreadsPerRow;\n  }\n  CUTLASS_DEVICE int thread_col() const {\n    return kElementsPerThread * (lane % kThreadsPerRow);\n  }\n  // load/store in gmem\n  template <typename RowMod>\n  CUTLASS_DEVICE void load(\n      Element const* ptr,\n      int64_t stride0,\n      RowMod row_mod) {\n    cutlass::arch::global_load<decltype(data), sizeof(data)>(\n        data, ptr + stride0 * row_mod(thread_row()) + thread_col(), true);\n  }\n  CUTLASS_DEVICE void load(Element const* ptr, int64_t stride0) {\n    load(ptr, stride0, [](int i) { return i; });\n  }\n  CUTLASS_DEVICE void store_line(Element* ptr) const {\n    cutlass::arch::global_store<decltype(data), sizeof(data)>(\n        data, ptr + thread_col(), true);\n  }\n  CUTLASS_DEVICE void store(Element* ptr, int64_t stride0) const {\n    cutlass::arch::global_store<decltype(data), sizeof(data)>(\n        data, ptr + stride0 * thread_row() + thread_col(), true);\n  }\n\n  // load/store in smem\n  template <int kStride0, int kStride1, typename ElementSmem>\n  CUTLASS_DEVICE void load_32bits(ElementSmem const* ptr) {\n    if constexpr (kStride1 == 1 && std::is_same<Element, ElementSmem>::value) {\n      cutlass::Array<ElementSmem, 4 / sizeof(ElementSmem)> frag32;\n      static_assert(sizeof(frag32) == 4);\n      CUTLASS_PRAGMA_UNROLL\n      for (int i = 0; i < kElementsPerThread / frag32.size(); ++i) {\n        frag32 =\n            *((decltype(frag32) const*)(ptr + thread_col() + frag32.size() * i +\n                                        kStride0 * thread_row()));\n        CUTLASS_PRAGMA_UNROLL\n        for (int j = 0; j < frag32.size(); ++j) {\n          data[frag32.size() * i + j] = Element(frag32[j]);\n        }\n      }\n    } else {\n      CUTLASS_PRAGMA_UNROLL\n      for (int col = 0; col < data.size(); ++col) {\n        data[col] = Element(\n            ptr[kStride0 * thread_row() + kStride1 * (thread_col() + col)]);\n      }\n    }\n  }\n  template <int kStride0, int kStride1, typename ElementSmem = Element>\n  CUTLASS_DEVICE void store_32bits(ElementSmem* ptr) const {\n    if constexpr (\n        kStride1 == 1 && sizeof(Element) == 2 &&\n        std::is_same<Element, ElementSmem>::value) {\n      // store packed as 32bits - Row-Major\n      uint32_t const* pack_ptr = reinterpret_cast<uint32_t const*>(&data);\n      uint32_t* smem_ptr =\n          (uint32_t*)(ptr + kStride0 * thread_row() + kStride1 * thread_col());\n      CUTLASS_PRAGMA_UNROLL\n      for (int c = 0; c < data.size() / 2; ++c) {\n        smem_ptr[c] = pack_ptr[c];\n      }\n    } else if constexpr (\n        kStride0 == 1 && sizeof(Element) == 2 && kRows == 2 &&\n        kElementsPerThread % 2 == 0 &&\n        std::is_same<Element, ElementSmem>::value) {\n      // store packed as 32bits - Col-Major\n      uint32_t const* pack_ptr = reinterpret_cast<uint32_t const*>(&data);\n      bool is_low_thread = threadIdx.x & kThreadsPerRow;\n      CUTLASS_PRAGMA_UNROLL\n      for (int c = 0; c < data.size() / 2; ++c) {\n        uint32_t my_val = pack_ptr[c];\n        uint32_t other_val =\n            __shfl_xor_sync(0xffffffff, my_val, kThreadsPerRow);\n        if (is_low_thread) {\n          my_val = (my_val & 0x0000FFFF) | (other_val << 16);\n        } else {\n          my_val = (my_val & 0xFFFF0000) | (other_val & 0xFFFF);\n        }\n        uint32_t* smem_ptr =\n            (uint32_t*)(ptr +\n                        kStride1 * (thread_col() + is_low_thread + 2 * c));\n        *smem_ptr = my_val;\n      }\n    } else {\n      // not optimized path\n      CUTLASS_PRAGMA_UNROLL\n      for (int col = 0; col < data.size(); ++col) {\n        ptr[kStride0 * thread_row() + kStride1 * (thread_col() + col)] =\n            ElementSmem(data[col]);\n      }\n    }\n  }\n  template <typename ElementOut>\n  CUTLASS_DEVICE WarpTensor<ElementOut, kRows, kCols> to() const {\n    cutlass::NumericArrayConverter<\n        ElementOut,\n        Element,\n        kElementsPerThread,\n        cutlass::FloatRoundStyle::round_to_nearest>\n        converter;\n\n    WarpTensor<ElementOut, kRows, kCols> out;\n    out.data = converter(data);\n    return out;\n  }\n\n  CUTLASS_DEVICE void print(int offs_row = 0) const {\n    for (int i = 0; i < 32; ++i) {\n      if (lane == i) {\n        printf(\n            \"[lane=%d][%d, %d:%d] = \",\n            int(lane),\n            int(thread_row() + offs_row),\n            int(thread_col()),\n            int(thread_col() + kElementsPerThread));\n        for (int j = 0; j < data.size(); ++j) {\n          // printf(\"0x%x \", uint32_t(data[j]));\n          printf(\"%f \", float(data[j]));\n        }\n        printf(\"\\n\");\n      }\n      __syncthreads();\n    }\n  }\n\n  template <typename Algo>\n  CUTLASS_DEVICE std::tuple<\n      WarpTensor<Element, kRows, kCols / 2>,\n      WarpTensor<uint8_t, kRows, kCols / 8>>\n  sparsify_pack(Algo algo) {\n    constexpr int kCount = kElementsPerThread;\n    auto dense_values = data;\n\n    WarpTensor<Element, kRows, kCols / 2> tensor_packed;\n    WarpTensor<uint8_t, kRows, kCols / 8> tensor_mdata;\n    uint8_t metadata = 0;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < kCount / 4; ++i) {\n      cutlass::Array<Element, 4> to_sparsify;\n      CUTLASS_PRAGMA_UNROLL\n      for (int j = 0; j < 4; ++j) {\n        to_sparsify[j] = dense_values[4 * i + j].get();\n      }\n      cutlass::Array<Element, 2> packed2;\n      int m = algo(to_sparsify, packed2);\n      metadata |= (m << (4 * i));\n      tensor_packed.data[2 * i] = packed2[0].get();\n      tensor_packed.data[2 * i + 1] = packed2[1].get();\n    }\n    tensor_mdata.data[0] = metadata;\n    return std::make_tuple(tensor_packed, tensor_mdata);\n  }\n\n  CUTLASS_DEVICE WarpTensor<Element, kRows, kCols / 2> sparsify_as(\n      WarpTensor<uint8_t, kRows, kCols / 8> mdata) const {\n    static_assert(sizeof(Element) == 2);\n    auto* ptr = reinterpret_cast<uint32_t const*>(&data);\n\n    WarpTensor<Element, kRows, kCols / 2> packed;\n\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < kElementsPerThread / 4; ++i) {\n      auto a = ptr[2 * i];\n      auto b = ptr[2 * i + 1];\n      auto sparseSelect = [&](uint8_t mdata_element) {\n        int m0 = mdata_element & 0x1;\n        int m1 = (mdata_element >> 1) & 0x1;\n        int out = ((a >> (16 * m0)) * (1 - m1) + (b >> (16 * m0)) * m1);\n        return reinterpret_cast<Element&>(out);\n      };\n      uint8_t mdata_i = mdata.data[i / 2].get() >> (4 * (i % 2));\n      packed.data[2 * i] = sparseSelect(mdata_i);\n      packed.data[2 * i + 1] = sparseSelect(mdata_i >> 2);\n    }\n    return packed;\n  }\n\n  CUTLASS_DEVICE WarpTensor<Element, kRows, kCols * 2> unpack(\n      WarpTensor<uint8_t, kRows, kCols * 2 / 8> mdata) const {\n    static_assert(sizeof(Element) == 2);\n\n    WarpTensor<Element, kRows, kCols * 2> unpacked;\n    auto* ptr_p = reinterpret_cast<uint32_t const*>(&data);\n    auto* ptr_unp = reinterpret_cast<uint32_t*>(&unpacked.data);\n\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < kElementsPerThread / 2; ++i) {\n      auto packed = ptr_p[i];\n      uint32_t p0 = packed & 0xFFFF;\n      uint32_t p1 = packed >> 16;\n      uint8_t mdata_i = mdata.data[i / 2].get() >> (4 * (i % 2));\n      uint32_t m0 = mdata_i & 0x3;\n      uint32_t m1 = (mdata_i >> 2) & 0x3;\n      p0 = p0 << ((m0 & 1) * 16);\n      p1 = p1 << ((m1 & 1) * 16);\n\n      uint32_t unp0 = 0;\n      uint32_t unp1 = 0;\n      if (m0 & 0x1) {\n        unp1 = p0;\n      } else {\n        unp0 = p0;\n      }\n      if (m1 & 0x1) {\n        unp1 += p1;\n      } else {\n        unp0 += p1;\n      }\n      ptr_unp[2 * i] = unp0;\n      ptr_unp[2 * i + 1] = unp1;\n    }\n    return unpacked;\n  }\n\n  template <typename BinaryOp>\n  CUTLASS_DEVICE std::tuple<\n      cutlass::Array<Element, kCols / 32>, // reduce elements\n      uint32_t // thread offset\n      >\n  all_reduce(BinaryOp binary_op) const {\n    // reduces across the first dimension (eg `out[i,k]=out[j,k]`)\n    WarpTensor<Element, kRows, kCols> red;\n    red.data = data;\n\n    CUTLASS_PRAGMA_UNROLL\n    for (int xor_lane = kThreadsPerRow; xor_lane < 32; xor_lane *= 2) {\n      CUTLASS_PRAGMA_UNROLL\n      for (int i = 0; i < red.data.size(); ++i) {\n        Element other_val = Element(\n            __shfl_xor_sync(0xffffffff, Element(red.data[i]), xor_lane));\n        red.data[i] = binary_op(red.data[i], other_val);\n      }\n    }\n\n    uint32_t offset = thread_col();\n    cutlass::Array<Element, kCols / 32> out;\n    if constexpr (kThreadsPerRow == 16) {\n      static constexpr int kOffset = kElementsPerThread / 2;\n      if (thread_row() == 1) {\n        offset += kOffset;\n        CUTLASS_PRAGMA_UNROLL\n        for (int i = 0; i < kOffset; ++i) {\n          out[i] = red.data[i + kOffset];\n        }\n      } else {\n        CUTLASS_PRAGMA_UNROLL\n        for (int i = 0; i < kOffset; ++i) {\n          out[i] = red.data[i];\n        }\n      }\n    } else {\n      static_assert(kThreadsPerRow == 16); // Only supported in that case\n    }\n\n    return std::make_tuple(out, offset);\n  }\n\n  template <typename BinaryOp>\n  CUTLASS_DEVICE Element reduce_line(BinaryOp binary_op) const {\n    Element reduced = data[0];\n    // local reduction\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 1; i < data.size(); ++i) {\n      reduced = binary_op(reduced, Element(data[i]));\n    }\n\n    // reduce with other lanes\n    CUTLASS_PRAGMA_UNROLL\n    for (int xor_lane = 1; xor_lane < kThreadsPerRow; xor_lane *= 2) {\n      Element other_val =\n          Element(__shfl_xor_sync(0xffffffff, reduced, xor_lane));\n      reduced = binary_op(reduced, other_val);\n    }\n    return reduced;\n  }\n\n  struct TileValueOrdered1d {\n    union {\n      struct {\n        Element value;\n        uint16_t pos;\n      } parts;\n      uint32_t raw;\n    };\n    CUTLASS_DEVICE bool operator<(TileValueOrdered1d const& other) const {\n      return parts.value < other.parts.value;\n    }\n    CUTLASS_DEVICE TileValueOrdered1d() {}\n  };\n\n  template <int N, int M, typename SortPreproc>\n  CUTLASS_DEVICE WarpTensor<Element, kRows, kCols> sparsify_dense(\n      SortPreproc sort_preproc) const {\n    static_assert(M == kElementsPerThread);\n\n    WarpTensor<Element, kRows, kCols> out;\n\n    cutlass::Array<TileValueOrdered1d, M> values_ordered;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < data.size(); ++i) {\n      auto& v = values_ordered[i];\n      v.parts.value = sort_preproc(data[i].get());\n      v.parts.pos = i;\n    }\n    StaticSort<M> sorter;\n    sorter(values_ordered);\n\n    // mask out smallest elements\n    uint32_t kept_mask = 0;\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = M - N; i < M; ++i) {\n      kept_mask |= (1 << values_ordered[i].parts.pos);\n    }\n    CUTLASS_PRAGMA_UNROLL\n    for (int i = 0; i < M; ++i) {\n      if (kept_mask & 0x1) {\n        out.data[i] = data[i].get();\n      } else {\n        out.data[i] = Element(0);\n      }\n      kept_mask = kept_mask >> 1;\n    }\n    return out;\n  }\n};\n\nCUTLASS_DEVICE void store_metadata_reordered(\n    WarpTensor<uint8_t, 16, 32 / 8> mdata_tensor,\n    uint8_t* mdata_ptr) {\n  // This function is explained in\n  // https://docs.google.com/spreadsheets/d/1JvEsw9QnoIvXctOnED3Gk0LFnIe8XJTnbCRamxvfFBw/edit?gid=1603247130#gid=1603247130\n  auto lane = mdata_tensor.lane;\n  static_assert(mdata_tensor.kElementsPerThread == 2);\n\n  uint16_t mdata_step0 = reinterpret_cast<uint16_t const&>(mdata_tensor.data);\n  uint16_t other_step0 = __shfl_xor_sync(0xffffffff, mdata_step0, 16);\n\n  // step1\n  uint16_t mdata_step1 = 0;\n  if (lane & 16) { // T16-T31\n    mdata_step1 = ((mdata_step0 & 0xF0F0) | ((other_step0 >> 4) & 0x0F0F));\n  } else { // T0-T15\n    mdata_step1 = ((mdata_step0 & 0x0F0F) | ((other_step0 << 4) & 0xF0F0));\n  }\n\n  // step2\n  uint16_t other_step1 = __shfl_xor_sync(0xffffffff, mdata_step1, 1);\n  uint16_t mdata_gmem = 0;\n  if (lane & 1) { // T1\n    mdata_gmem = ((mdata_step1 & 0xFF00) | ((other_step1 >> 8) & 0x00FF));\n  } else { // T0\n    mdata_gmem = ((mdata_step1 & 0x00FF) | ((other_step1 << 8) & 0xFF00));\n  }\n\n  // read to store to gmem\n  cutlass::arch::global_store<decltype(mdata_gmem), sizeof(mdata_gmem)>(\n      mdata_gmem,\n      mdata_ptr + (lane % 2) * 4 + ((lane % 16) / 2) * 8 + (lane / 16) * 2,\n      true);\n}\n\nstruct Identity {\n  template <typename T>\n  T CUTLASS_DEVICE operator()(T x) const {\n    return x;\n  }\n};\n\ntemplate <\n    int kSmemStride0,\n    int kSmemStride1,\n    int kNumRows,\n    int kWarpsPerCTA,\n    typename Algo,\n    typename Element,\n    typename PointwiseFn>\nCUTLASS_DEVICE void warp_dump_sparse_and_dense_from_smem_32cols(\n    Element const* smem,\n    Algo algo,\n    int32_t const* destination_idx_ptr,\n    // sparse part\n    uint8_t* sparse_bitmask_ptr,\n    int64_t sparse_bitmask_s0,\n    int64_t sparse_bitmask_s1,\n    Element* sparse_packed_ptr,\n    int64_t sparse_packed_s0,\n    // dense part\n    Element* dense_ptr,\n    int64_t dense_s0,\n    PointwiseFn pointwise_fn = Identity()) {\n  // 64x32 data is layed out like:\n  // row 0: [T0 (128 bits)][T1 (128 bits)][T2 (128 bits)]...\n  // row 1: [T4 (128 bits)][T5 (128 bits)]...\n  // ...\n  // row 8: [T0 (128 bits)][T1 (128 bits)]...\n  // ..\n  WarpTensor<Element, 8, 32> tensor;\n\n  cutlass::Array<int32_t, kNumRows / 8 / kWarpsPerCTA> destination_idx_array;\n  int warp_row = (threadIdx.x / 32) * tensor.kRows;\n  CUTLASS_PRAGMA_UNROLL\n  for (int row = 0; row < kNumRows; row += kWarpsPerCTA * tensor.kRows) {\n    cutlass::arch::global_load<int32_t, sizeof(destination_idx_array[0])>(\n        destination_idx_array[row / (kWarpsPerCTA * tensor.kRows)],\n        destination_idx_ptr + tensor.thread_row() + row + warp_row,\n        true);\n  }\n\n  CUTLASS_PRAGMA_UNROLL\n  for (int row = 0; row < kNumRows; row += kWarpsPerCTA * tensor.kRows) {\n    tensor.template load_32bits<kSmemStride0, kSmemStride1>(\n        smem + kSmemStride0 * (row + warp_row));\n    tensor.data = pointwise_fn(tensor.data);\n    // RF -> RF (sparsify)\n    auto [packed, bitmask] = tensor.sparsify_pack(algo);\n    int32_t destination_idx =\n        destination_idx_array[row / (kWarpsPerCTA * tensor.kRows)];\n    if (destination_idx >= 0) {\n      // shape: [cols/32, rows, 32/8] (b8)\n      int64_t coord0 = (tensor.thread_col()) / 32;\n      int64_t coord1 = destination_idx;\n      int64_t coord2 = (tensor.thread_col() % 32) / 8;\n      sparse_bitmask_ptr\n          [coord0 * sparse_bitmask_s0 + coord1 * sparse_bitmask_s1 + coord2] =\n              bitmask.data[0];\n      packed.store_line(sparse_packed_ptr + sparse_packed_s0 * destination_idx);\n    } else {\n      destination_idx = -(destination_idx + 1);\n      tensor.store_line(dense_ptr + dense_s0 * destination_idx);\n    }\n  }\n}\n} // namespace sp24\n} // namespace xformers\n"
  },
  {
    "path": "xformers/flash_attn_3/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "xformers/fwbw_overlap.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport atexit\nimport logging\nimport os\nimport queue\nimport threading\nimport traceback\nfrom collections.abc import Callable\nfrom dataclasses import dataclass\nfrom typing import Any, cast, ClassVar, overload, TypeVar, Union\n\nimport torch\nfrom typing_extensions import Unpack\n\ntry:\n    from deep_ep.utils import EventHandle, EventOverlap  # type: ignore\nexcept ImportError:\n    # If DeepEP is not available, recreate ourself those structures\n    class EventHandle:  # type: ignore[no-redef]\n        def __init__(self) -> None:\n            self._event = torch.cuda.Event()\n\n        def current_stream_wait(self) -> None:\n            self._event.wait()\n\n    class EventOverlap:  # type: ignore[no-redef]\n        def __init__(self, event: Union[EventHandle, None] = None) -> None:\n            self.event = event\n\n        def current_stream_wait(self) -> None:\n            assert self.event is not None\n            self.event.current_stream_wait()\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass EventOverlapHolder(torch.Tensor):\n    \"\"\"\n    Holds a CUDAEvent. Why does it need to be a tensor?\n    So that its `gradient` can also hold a CUDAEvent for\n    overlaps in the BW pass\n    \"\"\"\n\n    event_overlap: Union[EventOverlap, None]\n    _name: str\n    __slots__: list[str] = [\"event_overlap\", \"_name\"]\n\n    @classmethod\n    def capture(\n        cls,\n        device: torch.device,\n        name: str = \"\",\n    ) -> \"EventOverlapHolder\":\n        return EventOverlapHolder(\n            EventOverlap(EventHandle()),\n            device=device,\n            name=name,\n            requires_grad=False,\n        )\n\n    @staticmethod\n    def __new__(\n        cls: \"type[EventOverlapHolder]\",\n        event_overlap: Union[EventOverlap, None],\n        device: torch.device,\n        name: str = \"\",\n        requires_grad: bool = True,\n    ):\n        return torch.Tensor._make_wrapper_subclass(  # type: ignore[attr-defined]\n            cls,\n            size=[0],\n            device=device,\n            dtype=torch.float32,\n            requires_grad=requires_grad,\n        )\n\n    def __init__(\n        self,\n        event_overlap: Union[EventOverlap, None],\n        device: torch.device,\n        name: str = \"\",\n        requires_grad: bool = True,\n    ):\n        super().__init__()\n        self.event_overlap = event_overlap\n        self._name = name\n\n    def __tensor_flatten__(self):\n        return self.__slots__, ()\n\n    def __repr__(self) -> str:  # type: ignore\n        return f\"{self.__class__.__name__}({self._name})\"\n\n    def current_stream_wait(self) -> None:\n        if self.event_overlap is not None:\n            self.event_overlap.current_stream_wait()\n\n    __torch_function__ = torch._C._disabled_torch_function_impl  # type: ignore\n\n    @classmethod\n    def __torch_dispatch__(\n        cls, func: Any, types: Any, args: Any = (), kwargs: Any = None\n    ) -> Any:\n        if func._overloadpacket in [\n            torch.ops.aten.detach_,\n            torch.ops.aten.detach,\n            torch.ops.aten._to_copy,\n            torch.ops.aten.view,\n            torch.ops.aten._unsafe_view,\n        ]:\n            self = args[0]\n            assert isinstance(self, EventOverlapHolder)\n            return EventOverlapHolder(\n                self.event_overlap,\n                device=self.device,\n                name=self._name,\n                requires_grad=False,\n            )\n        if func._overloadpacket is torch.ops.aten.new_empty_strided:\n            self, shape, strides = args\n            return EventOverlapHolder(\n                None,\n                device=self.device,\n                name=\"new_empty_strided\",\n                requires_grad=False,\n            )\n\n        raise NotImplementedError(f\"{cls.__name__} does not support {func}\")\n\n\nclass _ExitCompute(torch.autograd.Function):\n    \"\"\"\n    Execution order:\n    [compute]\n    [exit_compute] <- here\n      [bw chunk]\n    [comms]\n    \"\"\"\n\n    @staticmethod\n    def forward(ctx: torch.autograd.function.FunctionCtx, *tensors: torch.Tensor):\n        device = next(x.device for x in tensors if x is not None)\n        holder = EventOverlapHolder.capture(device=device, name=\"exit_compute\")\n        ctx.set_materialize_grads(False)\n        return (holder, *tensors)\n\n    @staticmethod\n    def backward(  # type: ignore\n        ctx: torch.autograd.function.FunctionCtx,\n        gholder: Union[EventOverlapHolder, None],\n        *gtensors: torch.Tensor,\n    ):\n        # wait for comms to finish before doing the compute\n        if gholder is not None:\n            assert isinstance(gholder, EventOverlapHolder)\n            gholder.current_stream_wait()\n        return gtensors\n\n\nclass _EnterCompute(torch.autograd.Function):\n    \"\"\"\n    Execution order:\n    [comms]\n      [bw chunk]\n    [enter_compute] <- here\n    [compute]\n    \"\"\"\n\n    @staticmethod\n    def forward(\n        ctx: torch.autograd.function.FunctionCtx,\n        holder: EventOverlapHolder,\n        *tensors: torch.Tensor,\n    ):\n        holder.current_stream_wait()\n        ctx.set_materialize_grads(False)\n        ctx.event_overlap_requires_grad = holder.requires_grad  # type: ignore\n        if len(tensors) == 1:\n            return tensors[0]\n        return tensors\n\n    @staticmethod\n    def backward(ctx: torch.autograd.function.FunctionCtx, *gtensors: torch.Tensor):\n        if ctx.event_overlap_requires_grad:  # type: ignore\n            device = next(x.device for x in gtensors if x is not None)\n            gholder = EventOverlapHolder.capture(device=device, name=\"enter_compute(B)\")\n        else:\n            gholder = None\n        return gholder, *gtensors\n\n\nclass _FillGradientForOverlapHolder(torch.autograd.Function):\n    \"\"\"\n    If the OverlapHolder is not used (eg with 1 GPU) then it does\n    not get any gradient, and the BW pass is stuck. This ensures it does\n    not happen.\n    \"\"\"\n\n    @staticmethod\n    def forward(\n        ctx: torch.autograd.function.FunctionCtx,\n        holder: EventOverlapHolder,\n        *tensors: torch.Tensor,\n    ):\n        assert isinstance(holder, EventOverlapHolder)\n        holder = EventOverlapHolder(\n            holder.event_overlap, device=holder.device, name=holder._name\n        )\n        ctx.set_materialize_grads(False)\n        ctx.already_called = False  # type: ignore\n        return holder, *tensors\n\n    @staticmethod\n    def backward(  # type: ignore\n        ctx: torch.autograd.function.FunctionCtx,\n        gholder: Union[EventOverlapHolder, None],\n        *gtensors: torch.Tensor,\n    ):\n        assert not ctx.already_called, (  # type: ignore\n            \"this BW pass got called multiple times. This is \"\n            \"most likely a *very nasty* bug with the FW+BW overlap\"\n        )\n        ctx.already_called = True  # type: ignore\n\n        if gholder is None:\n            device = next(x.device for x in gtensors if x is not None)\n            gholder = EventOverlapHolder(\n                None,\n                device=device,\n                name=\"_FillGradientForOverlapHolder\",\n                requires_grad=False,\n            )\n        return gholder, *gtensors\n\n\ndef enter_comm(\n    *tensors: torch.Tensor,\n    name: str = \"comm\",\n) -> tuple[EventOverlapHolder, Unpack[tuple[torch.Tensor, ...]]]:\n    assert all(isinstance(x, torch.Tensor) for x in tensors)\n    tensors = _ExitCompute.apply(*tensors)  # type: ignore\n    tensors = enter_phase(name, *tensors)\n    return _FillGradientForOverlapHolder.apply(*tensors)  # type: ignore\n\n\n@overload\ndef enter_compute(\n    __overlap_holder: EventOverlapHolder,\n    __tensor0: torch.Tensor,\n    __tensor1: torch.Tensor,\n    *tensors: torch.Tensor,\n    name: str = \"compute\",\n) -> tuple[torch.Tensor, ...]:\n    pass\n\n\n@overload\ndef enter_compute(\n    overlap_holder: EventOverlapHolder,\n    tensor: torch.Tensor,\n    *,\n    name: str = \"compute\",\n) -> torch.Tensor:\n    pass\n\n\ndef enter_compute(  # type: ignore\n    overlap_holder: EventOverlapHolder,\n    *tensors: torch.Tensor,\n    name: str = \"compute\",\n) -> Union[torch.Tensor, tuple[torch.Tensor, ...]]:\n    assert all(isinstance(x, torch.Tensor) for x in tensors)\n    # Ensure we have at least one op\n    overlap_holder = overlap_holder.view_as(overlap_holder)  # type: ignore\n    overlap_holder, *compute_tensors = enter_phase(name, overlap_holder, *tensors)  # type: ignore\n    return _EnterCompute.apply(overlap_holder, *compute_tensors)  # type: ignore\n\n\n@dataclass\nclass PhaseBoundary:\n    fw_enter: str\n    arrived_sem: threading.BoundedSemaphore\n    unblock_sem: threading.BoundedSemaphore\n    fw_previous_boundary: \"Union[PhaseBoundary, None]\"\n    is_final: bool = True\n\n    def __post_init__(self) -> None:\n        if self.fw_previous_boundary is not None:\n            self.fw_previous_boundary.is_final = False\n\n    def __str__(self) -> str:\n        prev_name = \"None\"\n        if self.fw_previous_boundary is not None:\n            prev_name = self.fw_previous_boundary.fw_enter\n        return f\"PhaseBoundary({prev_name} --> {self.fw_enter})\"\n\n    def __call__(self) -> None:\n        with _CurrentForwardState.cv_on_bw:\n            self.unblock_sem.release()\n            _CurrentForwardState.cv_on_bw.wait()\n        if _CurrentForwardState.bw_exception is not None:\n            raise _CurrentForwardState.bw_exception\n        elif self.fw_previous_boundary is not None:\n            # wait for autograd to finish scheduling the next chunk\n            if not self.fw_previous_boundary.arrived_sem.acquire(timeout=3800):\n                raise RuntimeError(\n                    f\"FWBW overlap: autograd did not go from {self.fw_enter}-->{self.fw_previous_boundary.fw_enter}. \"\n                    \"Did you make sure to call `before_forward` before every forward?\"\n                )\n        else:\n            # is there is no previous one, wait for autograd to finish entirely\n            assert _CurrentForwardState.bw_done_semaphore is not None\n            if not _CurrentForwardState.bw_done_semaphore.acquire(timeout=3800):\n                raise RuntimeError(\n                    f\"FWBW overlap: autograd did not finish after crossing {self.fw_enter} in BW pass.\"\n                    \"Did you make sure to call `before_forward` before every forward?\"\n                )\n        _CurrentForwardState.bw_last_boundary = self.fw_previous_boundary\n\n\nclass InitialBw:\n    def __init__(self, trigger_bw: Callable[[], None]) -> None:\n        self.trigger_bw = trigger_bw\n\n    def __call__(self) -> None:\n        _CurrentForwardState.bw_chunks_wait = True\n\n        # trigger first BW chunk\n        # + retrieve the first boundary in the BW pass\n        with _CurrentForwardState.cv_on_bw:\n            _CurrentForwardState.bw_last_boundary = None\n            _CurrentForwardState.bw_done_semaphore = async_bw(self.trigger_bw)\n            del self.trigger_bw\n            _CurrentForwardState.cv_on_bw.wait()\n            if _CurrentForwardState.bw_exception is not None:\n                exception = _CurrentForwardState.bw_exception\n                _CurrentForwardState.bw_exception = None\n                raise exception\n\n\nclass _GlobalAutogradThread:\n    thread: Union[threading.Thread, None] = None\n    todo = queue.SimpleQueue()  # type: ignore\n    # how many free BW slots we have\n    # 0 if we are currently running a BW pass\n    sem = threading.BoundedSemaphore()\n\n    @classmethod\n    def run(cls) -> None:\n        while True:\n            bw_fn, release_when_done = cls.todo.get()\n            if bw_fn is None:  # exit signal\n                return\n            try:\n                with cls.sem:\n                    bw_fn()\n            except Exception as exc:\n                traceback.print_exc()\n                _CurrentForwardState.bw_exception = exc\n            finally:\n                del bw_fn\n                release_when_done.release()\n                # edge case: if there is only a single chunk\n                # in the BW pass. We need to wake-up the thread\n                # waiting for the first chunk\n                # If there are multiple chunks, `cv_on_bw` will be\n                # already notified and be `None`\n                with _CurrentForwardState.cv_on_bw:\n                    _CurrentForwardState.bw_last_boundary = None\n                    _CurrentForwardState.cv_on_bw.notify()\n\n    @classmethod\n    def cleanup_at_exit(cls) -> None:\n        logger.info(\"Shutting down FWBW overlap background thread\")\n        cls.todo.put((None, None))\n        if cls.thread is not None:\n            cls.thread.join()\n            cls.thread = None\n\n\ndef async_bw(backward_fn: Callable[[], None]) -> threading.Semaphore:\n    \"\"\"\n    You can wait for the backward to finish with `done_semaphore.acquire()`\n    \"\"\"\n    done_semaphore = threading.BoundedSemaphore()\n    done_semaphore.acquire()\n    _GlobalAutogradThread.todo.put((backward_fn, done_semaphore), block=False)\n    return done_semaphore\n\n\nclass _WaitInBW(torch.autograd.Function):\n    @staticmethod\n    def forward(\n        ctx: torch.autograd.function.FunctionCtx,\n        boundary: PhaseBoundary,\n        *x: torch.Tensor,\n    ) -> Any:\n        ctx.boundary = boundary  # type: ignore\n        ctx.backward_done = False  # type: ignore\n        if len(x) == 1:\n            return x[0]\n        return x\n\n    @staticmethod\n    def backward(ctx: torch.autograd.function.FunctionCtx, *gx: torch.Tensor) -> Any:\n        assert not ctx.backward_done  # type: ignore\n        ctx.backward_done = True  # type: ignore\n\n        if not _CurrentForwardState.bw_chunks_wait:\n            return None, *gx\n\n        boundary = cast(PhaseBoundary, ctx.boundary)  # type: ignore\n        boundary.arrived_sem.release()\n\n        with _CurrentForwardState.cv_on_bw:\n            _CurrentForwardState.bw_last_boundary = boundary\n            _CurrentForwardState.cv_on_bw.notify()\n\n        if not boundary.unblock_sem.acquire(timeout=3800):\n            raise RuntimeError(\n                f\"{boundary.fw_enter}: timed-out: unable to acquire semaphore to continue BW pass\"\n            )\n        return None, *gx\n\n\nclass _CurrentForwardState:\n    # if `True`, we record boundaries between chunks during the FW pass\n    record_fw_chunks: ClassVar[bool] = False\n    # if `True`, we wait in the BW pass\n    bw_chunks_wait: ClassVar[bool] = False\n    fw_previous_boundary: ClassVar[Union[PhaseBoundary, None]] = None\n    bw_last_boundary: ClassVar[Union[PhaseBoundary, Callable[[], None], None]] = None\n    bw_done_semaphore: ClassVar[Union[threading.Semaphore, None]] = None\n    bw_exception: ClassVar[Union[Exception, None]] = None\n    # triggered whenever a BW chunk is done (or exception happens)\n    cv_on_bw: ClassVar[threading.Condition] = threading.Condition()\n\n\ndef before_forward(record_fw_chunks: bool) -> None:\n    \"\"\"call this before entering a new FW pass\"\"\"\n    _CurrentForwardState.record_fw_chunks = record_fw_chunks\n    _CurrentForwardState.bw_chunks_wait = False\n    _CurrentForwardState.fw_previous_boundary = None\n    _CurrentForwardState.bw_last_boundary = None\n    _CurrentForwardState.bw_done_semaphore = None\n    _CurrentForwardState.bw_exception = None\n\n\ndef enter_phase(enter: str, *tensors: torch.Tensor) -> tuple[torch.Tensor, ...]:\n    \"Marks the transition to either comms or compute in the FW pass\"\n    if not _CurrentForwardState.record_fw_chunks:\n        return tensors\n    assert any(x.grad_fn is not None for x in tensors), (\n        f\"Entering phase `{enter}` but there is no `grad_fn` \"\n        \"on any of the input tensors. This can mean that there was no\"\n        \" operator in the previous phase?\"\n    )\n    flush_single_bw_chunk()\n\n    boundary = PhaseBoundary(\n        fw_enter=enter,\n        arrived_sem=threading.BoundedSemaphore(),\n        unblock_sem=threading.BoundedSemaphore(),\n        fw_previous_boundary=_CurrentForwardState.fw_previous_boundary,\n    )\n    boundary.arrived_sem.acquire()\n    boundary.unblock_sem.acquire()\n    tensors_after = _WaitInBW.apply(boundary, *tensors)  # type: ignore\n    _CurrentForwardState.fw_previous_boundary = boundary\n\n    return tuple(tensors_after)  # type: ignore\n\n\ndef flush_single_bw_chunk() -> bool:\n    last_b = _CurrentForwardState.bw_last_boundary\n    if last_b is None:\n        return False\n    last_b()  # trigger it\n    return True\n\n\ndef flush_pending_bw() -> None:\n    \"Flush all BW chunks that we can compute recursively\"\n    while flush_single_bw_chunk():\n        pass\n\n\nT = TypeVar(\"T\")\n\n\ndef overlap_fw_bw(\n    trigger_fw: Callable[[], T],\n    trigger_bw: Callable[[], None],\n    initial_bw_chunks: int = 0,\n) -> T:\n    try:\n        return _overlap_fw_bw(trigger_fw, trigger_bw, initial_bw_chunks)\n    finally:\n        _CurrentForwardState.bw_chunks_wait = False\n\n\ndef _overlap_fw_bw(\n    trigger_fw: Callable[[], T],\n    trigger_bw: Callable[[], None],\n    initial_bw_chunks: int = 0,\n) -> T:\n    if _GlobalAutogradThread.thread is None:\n        atexit.register(_GlobalAutogradThread.cleanup_at_exit)\n        _GlobalAutogradThread.thread = threading.Thread(\n            target=_GlobalAutogradThread.run,\n            name=\"overlap_fw_bw_autograd\",\n        )\n        _GlobalAutogradThread.thread.daemon = True\n        _GlobalAutogradThread.thread.start()\n        logger.info(\n            f\"Launched background autograd thread for FW+BW overlap\\n\"\n            f\"  parent_pid = {os.getpid()}\\n\"\n            f\"  thread_pid = {_GlobalAutogradThread.thread.native_id}\"\n        )\n\n    assert initial_bw_chunks >= 0\n    before_forward(True)\n    _CurrentForwardState.bw_last_boundary = InitialBw(trigger_bw)\n    for _ in range(initial_bw_chunks):\n        flush_single_bw_chunk()\n    outputs = trigger_fw()\n    while flush_single_bw_chunk():\n        pass\n    assert _CurrentForwardState.bw_last_boundary is None, \"BW never finished?\"\n    return outputs\n"
  },
  {
    "path": "xformers/info.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Dict\n\nimport torch\n\nfrom . import __version__, _cpp_lib, _is_opensource, _is_triton_available\nfrom .ops.common import OPERATORS_REGISTRY\nfrom .profiler.profiler_dcgm import DCGM_PROFILER_AVAILABLE\n\n\ndef get_features_status() -> Dict[str, str]:\n    features = {}\n    for op in OPERATORS_REGISTRY:\n        status_str = \"available\" if op.is_available() else \"unavailable\"\n        features[f\"{op.OPERATOR_CATEGORY}.{op.NAME}\"] = status_str\n    features[\"is_triton_available\"] = str(_is_triton_available())\n    return features\n\n\ndef print_info():\n    features = get_features_status()\n    print(f\"xFormers {__version__}\")\n    features[\"pytorch.version\"] = torch.__version__\n    if torch.cuda.is_available():\n        features[\"pytorch.cuda\"] = \"available\"\n        device = torch.cuda.current_device()\n        cap = torch.cuda.get_device_capability(device)\n        features[\"gpu.compute_capability\"] = \".\".join(str(ver) for ver in cap)\n        features[\"gpu.name\"] = torch.cuda.get_device_name(device)\n    else:\n        features[\"pytorch.cuda\"] = \"not available\"\n\n    features[\"dcgm_profiler\"] = (\n        \"available\" if DCGM_PROFILER_AVAILABLE else \"unavailable\"\n    )\n\n    build_info = _cpp_lib._build_metadata\n    if build_info is None and isinstance(\n        _cpp_lib._cpp_library_load_exception, _cpp_lib.xFormersInvalidLibException\n    ):\n        build_info = _cpp_lib._cpp_library_load_exception.build_info\n    if build_info is not None:\n        features[\"build.info\"] = \"available\"\n        features[\"build.cuda_version\"] = build_info.cuda_version\n        features[\"build.hip_version\"] = build_info.hip_version\n        features[\"build.python_version\"] = build_info.python_version\n        features[\"build.torch_version\"] = build_info.torch_version\n        for k, v in build_info.build_env.items():\n            features[f\"build.env.{k}\"] = v\n    else:\n        features[\"build.info\"] = \"none\"\n\n    try:\n        features[\"build.nvcc_version\"] = \".\".join(\n            str(v) for v in torch.ops.xformers._nvcc_build_version()\n        )\n    except (RuntimeError, AttributeError):\n        pass\n\n    if _is_opensource:\n        features[\"source.privacy\"] = \"open source\"\n    else:\n        features[\"source.privacy\"] = \"fairinternal\"\n\n    for name, status in features.items():\n        print(\"{:<50} {}\".format(f\"{name}:\", status))\n\n\nif __name__ == \"__main__\":\n    print_info()\n"
  },
  {
    "path": "xformers/ops/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport torch\n\nfrom .fmha import (\n    AttentionBias,\n    AttentionOp,\n    AttentionOpBase,\n    LowerTriangularMask,\n    memory_efficient_attention,\n    memory_efficient_attention_backward,\n    memory_efficient_attention_forward,\n    memory_efficient_attention_forward_requires_grad,\n    MemoryEfficientAttentionCkOp,\n    MemoryEfficientAttentionCutlassFwdFlashBwOp,\n    MemoryEfficientAttentionCutlassOp,\n    MemoryEfficientAttentionFlashAttentionOp,\n    MemoryEfficientAttentionSplitKCkOp,\n)\nfrom .indexing import index_select_cat, scaled_index_add\nfrom .modpar_layers import ColumnParallelLinear, RowParallelLinear\nfrom .rmsnorm import RMSNorm\nfrom .rope_padded import rope_padded\nfrom .seqpar import sequence_parallel_leading_matmul, sequence_parallel_trailing_matmul\nfrom .sequence_parallel_fused_ops import (\n    fused_allgather_and_anything,\n    fused_allgather_and_linear,\n    fused_anything_and_reducescatter,\n    fused_linear_and_reducescatter,\n)\nfrom .sp24 import Sparse24Tensor, sparsify24, sparsify24_like\nfrom .swiglu_op import SwiGLU, swiglu, SwiGLUEagerOp, SwiGLUOp, SwiGLUOpDispatch\nfrom .tiled_matmul import tiled_matmul\nfrom .unbind import get_stack_strides, stack_or_none, unbind\n\n# BW compatibility\nAttentionMask = AttentionBias\n\n\ndef masked_matmul(a, b, mask=None):\n    if torch.overrides.has_torch_function((a, b, mask)):\n        return torch.overrides.handle_torch_function(\n            masked_matmul, (a, b, mask), a, b, mask\n        )\n\n    att = a @ b\n\n    if mask is None:\n        return att\n\n    if mask.dtype == torch.bool:\n        if mask.ndim == 2:\n            mask = mask.unsqueeze(0).expand(att.shape[0], -1, -1)\n        # mask is presumed false == ignore\n        att[~mask] = float(\"-inf\")\n    else:\n        # mask is presumed additive\n        att += mask\n    return att\n\n\n__all__ = [\n    # fmha\n    \"AttentionBias\",\n    \"AttentionMask\",\n    \"AttentionOp\",\n    \"AttentionOpBase\",\n    \"LowerTriangularMask\",\n    \"MemoryEfficientAttentionCutlassFwdFlashBwOp\",\n    \"MemoryEfficientAttentionCutlassOp\",\n    \"MemoryEfficientAttentionFlashAttentionOp\",\n    \"MemoryEfficientAttentionCkOp\",\n    \"MemoryEfficientAttentionSplitKCkOp\",\n    \"memory_efficient_attention\",\n    \"memory_efficient_attention_backward\",\n    \"memory_efficient_attention_forward\",\n    \"memory_efficient_attention_forward_requires_grad\",\n    # indexing\n    \"index_select_cat\",\n    \"scaled_index_add\",\n    # modpar_layers\n    \"ColumnParallelLinear\",\n    \"RowParallelLinear\",\n    # rmsnorm\n    \"RMSNorm\",\n    # rope_padded\n    \"rope_padded\",\n    # seqpar\n    \"sequence_parallel_leading_matmul\",\n    \"sequence_parallel_trailing_matmul\",\n    # sequence_parallel_fused_ops\n    \"fused_allgather_and_anything\",\n    \"fused_allgather_and_linear\",\n    \"fused_anything_and_reducescatter\",\n    \"fused_linear_and_reducescatter\",\n    # swiglu_op\n    \"SwiGLU\",\n    \"SwiGLUEagerOp\",\n    \"SwiGLUOp\",\n    \"SwiGLUOpDispatch\",\n    \"swiglu\",\n    # tiled_matmul\n    \"tiled_matmul\",\n    # unbind\n    \"get_stack_strides\",\n    \"stack_or_none\",\n    \"unbind\",\n    # sp24\n    \"sparsify24\",\n    \"sparsify24_like\",\n    \"Sparse24Tensor\",\n    # .\n    \"masked_matmul\",\n]\n"
  },
  {
    "path": "xformers/ops/_triton/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\n# One reason this module is called `_triton` instead of just `triton` is this:\n# https://github.com/openai/triton/commit/c6040bcbd8a046785462481b2830b3fff5fc4aab\n\nfrom typing import TYPE_CHECKING\n\nimport xformers\n\nif TYPE_CHECKING or xformers._is_triton_available():\n    from .k_index_select_cat import index_select_cat_bwd, index_select_cat_fwd\n    from .k_scaled_index_add import scaled_index_add_bwd, scaled_index_add_fwd\nelse:\n    index_select_cat_fwd = index_select_cat_bwd = None\n    scaled_index_add_fwd = scaled_index_add_bwd = None\n"
  },
  {
    "path": "xformers/ops/_triton/k_index_select_cat.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport torch\nimport triton\nimport triton.language as tl\n\n\n@triton.jit\ndef index_select_cat_fwd_kernel(\n    output_ptr,  # *Pointer* to output tensor.\n    source_ptr,  # *Pointer* to source tensor.\n    index_ptr,  # *Pointer* to index tensor.\n    num_indices,\n    num_cols,\n    stride0,  # Stride information of source tensor.\n    stride1,\n    BLOCK_SIZE_INDEX: tl.constexpr,  # Number of indices each program should process.\n    BLOCK_SIZE_COL: tl.constexpr,  # Number of cols each program should process.\n):\n    pid0 = tl.program_id(axis=0)  # We use 2D launch grid\n    pid1 = tl.program_id(axis=1)\n\n    indices = pid0 * BLOCK_SIZE_INDEX + tl.arange(0, BLOCK_SIZE_INDEX)\n    rows = tl.load(index_ptr + indices, mask=(indices < num_indices))\n    cols = pid1 * BLOCK_SIZE_COL + tl.arange(0, BLOCK_SIZE_COL)\n\n    source_offsets = source_ptr + rows[:, None] * stride0 + cols[None, :] * stride1\n    mask = (indices[:, None] < num_indices) & (cols[None, :] < num_cols)\n    output = tl.load(source_offsets, mask=mask)\n\n    output_offsets = output_ptr + indices[:, None] * stride0 + cols[None, :] * stride1\n    tl.store(output_offsets, output, mask=mask)\n\n\ndef index_select_cat_fwd(\n    output: torch.Tensor,\n    source: torch.Tensor,\n    index: torch.Tensor,\n):\n    if not (source.is_cuda and index.is_cuda):\n        raise ValueError(\"The index tensor and the source tensor must be of type CUDA!\")\n\n    if not source.ndim == 2:\n        raise ValueError(f\"Expected 2-dimensional tensor, got {source.ndim}.\")\n    if not index.ndim == 1:\n        raise ValueError(f\"Expected 1-dimensional tensor, got {index.ndim}.\")\n\n    num_rows, num_cols = source.shape\n    num_indices = index.shape[0]\n\n    if not num_indices < num_rows:\n        raise ValueError(\n            \"The number of indices cannot exceed the number of rows in the source matrix.\"\n        )\n\n    stride0, stride1 = source.stride(0), source.stride(1)\n\n    def grid(meta):\n        return (\n            triton.cdiv(num_indices, meta[\"BLOCK_SIZE_INDEX\"]),\n            triton.cdiv(num_cols, meta[\"BLOCK_SIZE_COL\"]),\n        )\n\n    index_select_cat_fwd_kernel[grid](\n        output,\n        source,\n        index,\n        num_indices,\n        num_cols,\n        stride0,\n        stride1,\n        BLOCK_SIZE_INDEX=1,\n        BLOCK_SIZE_COL=512,\n    )\n\n    return output\n\n\n@triton.jit\ndef index_select_cat_bwd_kernel(\n    grad_source_ptr,  # *Pointer* to grad_source tensor.\n    index_ptr,  # *Pointer* to index tensor.\n    grad_output_ptr,  # *Pointer* to grad_output tensor.\n    num_rows,\n    num_indices,\n    num_cols,\n    stride0,  # Stride information of input and source tensor.\n    stride1,\n    BLOCK_SIZE_INDEX: tl.constexpr,  # Number of indices each program should process.\n    BLOCK_SIZE_COL: tl.constexpr,  # Number of cols each program should process.\n):\n    pid0 = tl.program_id(axis=0)  # We use 3D launch grid\n    pid1 = tl.program_id(axis=1)\n\n    cols = pid1 * BLOCK_SIZE_COL + tl.arange(0, BLOCK_SIZE_COL)\n\n    # load grad_output\n    grad_output_indices = pid0 * BLOCK_SIZE_INDEX + tl.arange(0, BLOCK_SIZE_INDEX)\n    grad_output_offsets = (\n        grad_output_ptr\n        + grad_output_indices[:, None] * stride0\n        + cols[None, :] * stride1\n    )\n    grad_output_mask = (grad_output_indices[:, None] < num_indices) & (\n        cols[None, :] < num_cols\n    )\n    grad_output = tl.load(grad_output_offsets, mask=grad_output_mask).to(tl.float32)\n\n    # select indices from grad_source\n    grad_source_indices = tl.load(\n        index_ptr + grad_output_indices, mask=(grad_output_indices < num_indices)\n    )\n    grad_source_offsets = (\n        grad_source_ptr\n        + grad_source_indices[:, None] * stride0\n        + cols[None, :] * stride1\n    )\n\n    # compute scaled index add and save\n    tl.store(grad_source_offsets, grad_output, mask=grad_output_mask)\n\n\ndef index_select_cat_bwd(\n    grad_source: torch.Tensor,\n    index: torch.Tensor,\n    grad_output: torch.Tensor,\n):\n    if not (grad_source.is_cuda and grad_output.is_cuda):\n        raise ValueError(\"The grad_source and grad_output tensor must be of type CUDA!\")\n\n    if not (grad_source.ndim == 2 and grad_output.ndim == 2):\n        raise ValueError(\n            f\"The grad_source and grad_output must be three-dimensional \"\n            f\"(got {grad_source.ndim} and {grad_output.ndim})!\"\n        )\n    if not grad_source.shape[1] == grad_output.shape[1]:\n        raise ValueError(\n            f\"The number of elements along dimension 1 of grad_source and grad_output must be the same \"\n            f\"(got {grad_source.shape[1]} and {grad_output.shape[1]})\"\n        )\n\n    num_rows, num_cols = grad_source.shape\n    num_indices, num_cols = grad_output.shape\n    if not num_rows >= num_indices:\n        raise ValueError(\n            f\"The number of elements along dimension 0 of grad_source must be larger than that of grad_output \"\n            f\"(got {num_rows} and {num_indices})!\"\n        )\n    if not index.shape[0] == num_indices:\n        raise ValueError(\n            f\"The number of indices and the number of elements along dimension 0 of grad_output must match \"\n            f\"(got {index.shape[0]} and {num_indices})!\"\n        )\n\n    stride0, stride1 = grad_source.stride(0), grad_source.stride(1)\n    if not (grad_output.stride(0) == stride0 and grad_output.stride(1) == stride1):\n        raise ValueError(\n            f\"The strides of the grad_source and grad_output tensors must match \"\n            f\"(got {stride0} vs. {grad_output.stride(0)}, {stride1} vs. {grad_output.stride(1)})!\"\n        )\n\n    def grid(meta):\n        return (\n            triton.cdiv(num_indices, meta[\"BLOCK_SIZE_INDEX\"]),\n            triton.cdiv(num_cols, meta[\"BLOCK_SIZE_COL\"]),\n        )\n\n    index_select_cat_bwd_kernel[grid](\n        grad_source,\n        index,\n        grad_output,\n        num_rows,\n        num_indices,\n        num_cols,\n        grad_source.stride(0),\n        grad_source.stride(1),\n        BLOCK_SIZE_INDEX=1,\n        BLOCK_SIZE_COL=512,\n    )\n\n    return\n"
  },
  {
    "path": "xformers/ops/_triton/k_scaled_index_add.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional\n\nimport torch\nimport triton\nimport triton.language as tl\n\n\n@triton.jit\ndef scaled_index_add_fwd_kernel(\n    input_ptr,  # *Pointer* to input tensor.\n    index_ptr,  # *Pointer* to index tensor.\n    source_ptr,  # *Pointer* to source tensor.\n    scaling_ptr,  # *Pointer* to the scaling tensor.\n    alpha,\n    num_inp_indices,\n    num_src_indices,\n    num_rows,\n    num_cols,\n    stride0,  # Stride information of input and source tensor.\n    stride1,\n    stride2,\n    BLOCK_SIZE_INDEX: tl.constexpr,  # Number of indices each program should process.\n    BLOCK_SIZE_ROW: tl.constexpr,  # Number of rows each program should process.\n    BLOCK_SIZE_COL: tl.constexpr,  # Number of cols each program should process.\n    HAS_SCALING: tl.constexpr,  # Boolean indicating if the scaling factor is present.\n):\n    pid0 = tl.program_id(axis=0)  # We use 3D launch grid\n    pid1 = tl.program_id(axis=1)\n    pid2 = tl.program_id(axis=2)\n\n    rows = pid1 * BLOCK_SIZE_ROW + tl.arange(0, BLOCK_SIZE_ROW)\n    cols = pid2 * BLOCK_SIZE_COL + tl.arange(0, BLOCK_SIZE_COL)\n\n    # load source\n    source_indices = pid0 * BLOCK_SIZE_INDEX + tl.arange(0, BLOCK_SIZE_INDEX)\n    source_offsets = (\n        source_ptr\n        + source_indices[:, None, None] * stride0\n        + rows[None, :, None] * stride1\n        + cols[None, None, :] * stride2\n    )\n    source_mask = (\n        (source_indices[:, None, None] < num_src_indices)\n        & (rows[None, :, None] < num_rows)\n        & (cols[None, None, :] < num_cols)\n    )\n    source = tl.load(source_offsets, mask=source_mask).to(tl.float32)\n\n    # load input\n    input_indices = tl.load(\n        index_ptr + source_indices, mask=(source_indices < num_src_indices)\n    )\n    input_offsets = (\n        input_ptr\n        + input_indices[:, None, None] * stride0\n        + rows[None, :, None] * stride1\n        + cols[None, None, :] * stride2\n    )\n    x = tl.load(input_offsets, mask=source_mask).to(tl.float32)\n\n    # compute scaled index add and save\n    if HAS_SCALING:\n        scaling = tl.load(\n            scaling_ptr + cols[None, None, :] * stride2,\n            mask=(cols[None, None, :] < num_cols),\n        ).to(tl.float32)\n        tl.store(input_offsets, x + alpha * scaling * source, mask=source_mask)\n    else:\n        tl.store(input_offsets, x + alpha * source, mask=source_mask)\n\n\ndef scaled_index_add_fwd(\n    x: torch.Tensor,\n    index: torch.Tensor,\n    source: torch.Tensor,\n    scaling: Optional[torch.Tensor],\n    alpha: float,\n):\n    if not (x.is_cuda and index.is_cuda and source.is_cuda):\n        raise ValueError(\n            \"The input tensor, the index tensor and the source tensor must be of type CUDA!\"\n        )\n\n    if not (x.ndim == 3 and source.ndim == 3):\n        raise ValueError(\n            f\"The input and source must be three-dimensional (got {x.ndim} and {source.ndim})!\"\n        )\n    if not x.shape[1] == source.shape[1]:\n        raise ValueError(\n            f\"The number of elements along dimension 1 of the input and source must be the same \"\n            f\"(got {x.shape[1], } and {source.shape[1], })!\"\n        )\n    if not x.shape[2] == source.shape[2]:\n        raise ValueError(\n            f\"The number of elements along dimension 2 of the input and source must be the same \"\n            f\"(got {x.shape[2], } and {source.shape[2], })!\"\n        )\n\n    num_inp_indices, num_rows, num_cols = x.shape\n    num_src_indices, num_rows, num_cols = source.shape\n    if not num_inp_indices >= num_src_indices:\n        raise ValueError(\n            f\"The number of elements along dimension 0 of the input must be larger than that of source \"\n            f\"(got {num_inp_indices} and {num_src_indices})!\"\n        )\n    if not index.shape[0] == num_src_indices:\n        raise ValueError(\n            f\"The number of indices and source tensors must match (got {len(index)} and {len(source)})!\"\n        )\n\n    stride0, stride1, stride2 = x.stride(0), x.stride(1), x.stride(2)\n    if not (\n        source.stride(0) == stride0\n        and source.stride(1) == stride1\n        and source.stride(2) == stride2\n    ):\n        raise ValueError(\n            f\"The strides of the source and input tensors must match (got {source.stride(0)} vs. {stride0}, \"\n            f\"{source.stride(1)} vs. {stride1}, {source.stride(2)} vs. {stride2})!\"\n        )\n\n    if scaling is None:\n        HAS_SCALING = False\n    else:\n        HAS_SCALING = True\n        if not scaling.is_cuda:\n            raise ValueError(\"The scaling tensor must be of type CUDA!\")\n        if not (scaling.ndim == 1 and scaling.numel() == num_cols):\n            raise ValueError(\n                f\"The scaling tensor must be a 1-dimensional tensor (got {scaling.ndim}) and its size \"\n                f\"must be equal to the size of dimension 2 of source (got {scaling.numel()} vs. {num_cols}).\"\n            )\n        if not scaling.stride(0) == stride2:\n            raise ValueError(\n                f\"The stride of scaling must match the stride2 of input (got {scaling.stride(0)} vs. {stride2})\"\n            )\n\n    if not index.ndim == 1:\n        raise ValueError(f\"The index must be one-dimensional (got {index.ndim})!\")\n\n    def grid(meta):\n        return (\n            triton.cdiv(num_src_indices, meta[\"BLOCK_SIZE_INDEX\"]),\n            triton.cdiv(num_rows, meta[\"BLOCK_SIZE_ROW\"]),\n            triton.cdiv(num_cols, meta[\"BLOCK_SIZE_COL\"]),\n        )\n\n    scaled_index_add_fwd_kernel[grid](\n        x,\n        index,\n        source,\n        scaling,\n        alpha,\n        num_inp_indices,\n        num_src_indices,\n        num_rows,\n        num_cols,\n        x.stride(0),\n        x.stride(1),\n        x.stride(2),\n        BLOCK_SIZE_INDEX=1,\n        BLOCK_SIZE_ROW=1,\n        BLOCK_SIZE_COL=512,\n        HAS_SCALING=HAS_SCALING,\n    )\n\n    return\n\n\n@triton.jit\ndef scaled_index_add_bwd_kernel(\n    grad_output_ptr,  # *Pointer* to input tensor.\n    grad_source_ptr,  # *Pointer* to index tensor.\n    grad_scaling_ptr,  # *Pointer* to source tensor.\n    source_ptr,  # *Pointer* to the source tensor.\n    scaling_ptr,  # *Pointer* to the scaling tensor.\n    index_ptr,\n    alpha,\n    num_inp_indices,\n    num_src_indices,\n    num_rows,\n    num_cols,\n    stride0,  # Stride information of input and source tensor.\n    stride1,\n    stride2,\n    BLOCK_SIZE_INDEX: tl.constexpr,  # Number of indices each program should process.\n    BLOCK_SIZE_ROW: tl.constexpr,  # Number of rows each program should process.\n    BLOCK_SIZE_COL: tl.constexpr,  # Number of cols each program should process.\n    HAS_SCALING: tl.constexpr,  # Boolean indicating if the scaling factor is present.\n):\n    pid0 = tl.program_id(axis=0)  # We use 3D launch grid\n    pid1 = tl.program_id(axis=1)\n    pid2 = tl.program_id(axis=2)\n\n    rows = pid1 * BLOCK_SIZE_ROW + tl.arange(0, BLOCK_SIZE_ROW)\n    cols = pid2 * BLOCK_SIZE_COL + tl.arange(0, BLOCK_SIZE_COL)\n\n    # load source\n    source_indices = pid0 * BLOCK_SIZE_INDEX + tl.arange(0, BLOCK_SIZE_INDEX)\n    source_offsets = (\n        source_ptr\n        + source_indices[:, None, None] * stride0\n        + rows[None, :, None] * stride1\n        + cols[None, None, :] * stride2\n    )\n    source_mask = (\n        (source_indices[:, None, None] < num_src_indices)\n        & (rows[None, :, None] < num_rows)\n        & (cols[None, None, :] < num_cols)\n    )\n    source = tl.load(source_offsets, mask=source_mask).to(tl.float32)\n\n    # load grad_output\n    grad_output_indices = tl.load(\n        index_ptr + source_indices, mask=(source_indices < num_src_indices)\n    )\n    grad_output_offsets = (\n        grad_output_ptr\n        + grad_output_indices * stride0\n        + rows[None, :, None] * stride1\n        + cols[None, None, :] * stride2\n    )\n    grad_output = tl.load(grad_output_offsets, mask=source_mask).to(tl.float32)\n\n    # compute gradient\n    grad_source_offsets = (\n        grad_source_ptr\n        + source_indices[:, None, None] * stride0\n        + rows[None, :, None] * stride1\n        + cols[None, None, :] * stride2\n    )\n    if HAS_SCALING:\n        scaling = tl.load(\n            scaling_ptr + cols[None, None, :] * stride2,\n            mask=(cols[None, None, :] < num_cols),\n        ).to(tl.float32)\n\n        tl.store(grad_source_offsets, alpha * grad_output * scaling, mask=source_mask)\n\n        grad_scaling_offsets = (\n            grad_scaling_ptr\n            + source_indices[:, None, None] * stride0\n            + rows[None, :, None] * stride1\n            + cols[None, None, :] * stride2\n        )\n        tl.store(grad_scaling_offsets, alpha * grad_output * source, mask=source_mask)\n    else:\n        tl.store(grad_source_offsets, alpha * grad_output, mask=source_mask)\n\n\ndef scaled_index_add_bwd(\n    grad_output: torch.Tensor,\n    grad_source: torch.Tensor,\n    grad_scaling: Optional[torch.Tensor],\n    source: torch.Tensor,\n    scaling: Optional[torch.Tensor],\n    index: torch.Tensor,\n    alpha: float,\n):\n    if not (grad_output.is_cuda and grad_source.is_cuda):\n        raise ValueError(\n            \"The grad_output tensor and grad_source tensor must be of type CUDA!\"\n        )\n\n    if not (grad_output.ndim == 3 and source.ndim == 3):\n        raise ValueError(\n            f\"The input and source must be three-dimensional (got {grad_output.ndim} and {source.ndim})!\"\n        )\n\n    if not grad_output.shape[1] == source.shape[1]:\n        raise ValueError(\n            f\"The number of elements along dimension 1 of the input and source must be the same \"\n            f\"(got {grad_output.shape[1], } and {source.shape[1], })!\"\n        )\n    if not grad_output.shape[2] == source.shape[2]:\n        raise ValueError(\n            f\"The number of elements along dimension 2 of the input and source must be the same \"\n            f\"(got {grad_output.shape[2], } and {source.shape[2], })!\"\n        )\n\n    num_inp_indices, num_rows, num_cols = grad_output.shape\n    num_src_indices, num_rows, num_cols = source.shape\n    if not num_inp_indices >= num_src_indices:\n        raise ValueError(\n            f\"The number of elements along dimension 0 of the input must be larger than that of source \"\n            f\"(got {num_inp_indices} and {num_src_indices})!\"\n        )\n\n    stride0, stride1, stride2 = source.stride(0), source.stride(1), source.stride(2)\n    if not (\n        grad_output.stride(0) == stride0\n        and grad_output.stride(1) == stride1\n        and grad_output.stride(2) == stride2\n    ):\n        raise ValueError(\n            f\"The strides of grad_output and source must match \"\n            f\"(got {grad_output.stride(0)} vs {stride0}, {grad_output.stride(1)} vs {stride1}, \"\n            f\"{grad_output.stride(2)} vs {stride2})!\"\n        )\n    if not (\n        grad_source.stride(0) == stride0\n        and grad_source.stride(1) == stride1\n        and grad_source.stride(2) == stride2\n    ):\n        raise ValueError(\n            f\"The strides of grad_source and source must match \"\n            f\"(got {grad_source.stride(0)} vs {stride0}, {grad_source.stride(1)} vs {stride1}, \"\n            f\"{grad_source.stride(2)} vs {stride2})!\"\n        )\n\n    if scaling is not None and grad_scaling is not None:\n        HAS_SCALING = True\n        if not grad_scaling.is_cuda:\n            raise ValueError(\"The scaling tensor must be of type CUDA!\")\n        if not (\n            grad_scaling.stride(0) == stride0\n            and grad_scaling.stride(1) == stride1\n            and grad_scaling.stride(2) == stride2\n        ):\n            raise ValueError(\n                f\"The strides of grad_scaling and source must match \"\n                f\"(got {grad_scaling.stride(0)} vs {stride0}, {grad_scaling.stride(1)} vs {stride1}, \"\n                f\"{grad_scaling.stride(2)} vs {stride2})!\"\n            )\n        if not scaling.stride(0) == stride2:\n            raise ValueError(\n                f\"The stride of scaling must match stride2 of source (got {scaling.stride(0)} vs. {stride2})!\"\n            )\n    else:\n        HAS_SCALING = False\n\n    def grid(meta):\n        return (\n            triton.cdiv(num_src_indices, meta[\"BLOCK_SIZE_INDEX\"]),\n            triton.cdiv(num_rows, meta[\"BLOCK_SIZE_ROW\"]),\n            triton.cdiv(num_cols, meta[\"BLOCK_SIZE_COL\"]),\n        )\n\n    scaled_index_add_bwd_kernel[grid](\n        grad_output,\n        grad_source,\n        grad_scaling,\n        source,\n        scaling,\n        index,\n        alpha,\n        num_inp_indices,\n        num_src_indices,\n        num_rows,\n        num_cols,\n        stride0,\n        stride1,\n        stride2,\n        BLOCK_SIZE_INDEX=1,\n        BLOCK_SIZE_ROW=1,\n        BLOCK_SIZE_COL=512,\n        HAS_SCALING=HAS_SCALING,\n    )\n\n    return\n"
  },
  {
    "path": "xformers/ops/_triton/matmul_perf_model.py",
    "content": "# Source: https://github.com/triton-lang/kernels/blob/main/kernels/matmul_perf_model.py\n# License: MIT from triton-lang/kernels\n\"\"\"\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\"\"\"\n\n# @lint-ignore-every LICENSELINT\n\n# flake8: noqa\n# pyre-ignore-all-errors\n# fmt: off\nimport functools\nimport heapq\n\nimport torch\nfrom triton import cdiv\nfrom triton.runtime import driver\nfrom triton.testing import (\n    get_dram_gbps,\n    get_max_simd_tflops,\n    get_max_tensorcore_tflops,\n    nvsmi,\n)\n\n\n@functools.lru_cache()\ndef get_clock_rate_in_khz():\n    try:\n        return nvsmi([\"clocks.max.sm\"])[0] * 1e3\n    except FileNotFoundError:\n        import pynvml\n\n        pynvml.nvmlInit()\n        handle = pynvml.nvmlDeviceGetHandleByIndex(0)\n        return pynvml.nvmlDeviceGetMaxClockInfo(handle, pynvml.NVML_CLOCK_SM) * 1e3\n\n\ndef get_tensorcore_tflops(device, num_ctas, num_warps, dtype):\n    \"\"\"return compute throughput in TOPS\"\"\"\n    total_warps = num_ctas * min(num_warps, 4)\n    num_subcores = (\n        driver.active.utils.get_device_properties(device)[\"multiprocessor_count\"] * 4\n    )  # on recent GPUs\n    tflops = (\n        min(num_subcores, total_warps)\n        / num_subcores\n        * get_max_tensorcore_tflops(dtype, get_clock_rate_in_khz(), device)\n    )\n    return tflops\n\n\ndef get_simd_tflops(device, num_ctas, num_warps, dtype):\n    \"\"\"return compute throughput in TOPS\"\"\"\n    total_warps = num_ctas * min(num_warps, 4)\n    num_subcores = (\n        driver.active.utils.get_device_properties(device)[\"multiprocessor_count\"] * 4\n    )  # on recent GPUs\n    tflops = (\n        min(num_subcores, total_warps)\n        / num_subcores\n        * get_max_simd_tflops(dtype, get_clock_rate_in_khz(), device)\n    )\n    return tflops\n\n\ndef get_tflops(device, num_ctas, num_warps, dtype):\n    capability = torch.cuda.get_device_capability(device)\n    if capability[0] < 8 and dtype == torch.float32:\n        return get_simd_tflops(device, num_ctas, num_warps, dtype)\n    return get_tensorcore_tflops(device, num_ctas, num_warps, dtype)\n\n\ndef estimate_matmul_time(\n    # backend, device,\n    num_warps,\n    num_stages,  #\n    A,\n    B,\n    C,  #\n    M,\n    N,\n    K,  #\n    BLOCK_M,\n    BLOCK_N,\n    BLOCK_K,\n    SPLIT_K,  #\n    debug=False,\n    **kwargs,  #\n):\n    \"\"\"return estimated running time in ms\n    = max(compute, loading) + store\"\"\"\n    device = torch.cuda.current_device()\n    dtype = A.dtype\n    dtsize = A.element_size()\n\n    num_cta_m = cdiv(M, BLOCK_M)\n    num_cta_n = cdiv(N, BLOCK_N)\n    num_cta_k = SPLIT_K\n    num_ctas = num_cta_m * num_cta_n * num_cta_k\n\n    # If the input is smaller than the block size\n    M, N = max(M, BLOCK_M), max(N, BLOCK_N)\n\n    # time to compute\n    total_ops = 2 * M * N * K / (1024 * 1024 * 1024)  # GOPS\n    tput = get_tflops(device, num_ctas, num_warps, dtype)\n    compute_ms = total_ops / tput\n\n    # time to load data\n    num_sm = driver.active.utils.get_device_properties(device)[\"multiprocessor_count\"]\n    active_cta_ratio = min(1, num_ctas / num_sm)\n    active_cta_ratio_bw1 = min(\n        1, num_ctas / 32\n    )  # 32 active ctas are enough to saturate\n    active_cta_ratio_bw2 = max(\n        min(1, (num_ctas - 32) / (108 - 32)), 0\n    )  # 32-108, remaining 5%\n    dram_bw = get_dram_gbps(device) * (\n        active_cta_ratio_bw1 * 0.95 + active_cta_ratio_bw2 * 0.05\n    )  # in GB/s\n    l2_bw = dram_bw * 4  # rough estimation (should be 4.7 for A100?)\n    # assume 80% of (following) loads are in L2 cache\n    load_a_dram = M * K * dtsize * (1 + 0.2 * (num_cta_n - 1))\n    load_a_l2 = M * K * dtsize * 0.8 * (num_cta_n - 1)\n    load_b_dram = N * K * dtsize * (1 + 0.2 * (num_cta_m - 1))\n    load_b_l2 = N * K * dtsize * 0.8 * (num_cta_m - 1)\n    # total\n    total_dram = (load_a_dram + load_b_dram) / (1024 * 1024)  # MB\n    total_l2 = (load_a_l2 + load_b_l2) / (1024 * 1024)\n    # loading time in ms\n    load_ms = total_dram / dram_bw + total_l2 / l2_bw\n\n    # estimate storing time\n    store_bw = dram_bw * 0.6  # :o\n    store_c_dram = M * N * dtsize * SPLIT_K / (1024 * 1024)  # MB\n    if SPLIT_K == 1:\n        store_ms = store_c_dram / store_bw\n    else:\n        reduce_bw = store_bw\n        store_ms = store_c_dram / reduce_bw\n        # c.zero_()\n        zero_ms = M * N * 2 / (1024 * 1024) / store_bw\n        store_ms += zero_ms\n\n    total_time_ms = max(compute_ms, load_ms) + store_ms\n    if debug:\n        print(\n            f\"Total time: {total_time_ms}ms, compute time: {compute_ms}ms, \"\n            f\"loading time: {load_ms}ms, store time: {store_ms}ms, \"\n            f\"Activate CTAs: {active_cta_ratio*100}%\"\n        )\n    return total_time_ms\n\n\ndef early_config_prune(configs, named_args, **kwargs):\n    device = torch.cuda.current_device()\n    capability = torch.cuda.get_device_capability()\n    # BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps, num_stages\n    dtsize = named_args[\"A\"].element_size()\n    dtype = named_args[\"A\"].dtype\n\n    # 1. make sure we have enough smem\n    pruned_configs = []\n    for config in configs:\n        kw = config.kwargs\n        BLOCK_M, BLOCK_N, BLOCK_K, num_stages = (\n            kw[\"BLOCK_M\"],\n            kw[\"BLOCK_N\"],\n            kw[\"BLOCK_K\"],\n            config.num_stages,\n        )\n\n        max_shared_memory = driver.active.utils.get_device_properties(device)[\n            \"max_shared_mem\"\n        ]\n        required_shared_memory = (BLOCK_M + BLOCK_N) * BLOCK_K * num_stages * dtsize\n        if required_shared_memory <= max_shared_memory:\n            pruned_configs.append(config)\n    configs = pruned_configs\n\n    # Some dtypes do not allow atomic_add\n    if dtype not in [torch.float16, torch.float32]:\n        configs = [config for config in configs if config.kwargs[\"SPLIT_K\"] == 1]\n\n    # group configs by (BLOCK_M,_N,_K, SPLIT_K, num_warps)\n    configs_map = {}\n    for config in configs:\n        kw = config.kwargs\n        BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps, num_stages = (\n            kw[\"BLOCK_M\"],\n            kw[\"BLOCK_N\"],\n            kw[\"BLOCK_K\"],\n            kw[\"SPLIT_K\"],\n            config.num_warps,\n            config.num_stages,\n        )\n\n        key = (BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps)\n        if key in configs_map:\n            configs_map[key].append((config, num_stages))\n        else:\n            configs_map[key] = [(config, num_stages)]\n\n    pruned_configs = []\n    for k, v in configs_map.items():\n        BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps = k\n        if capability[0] >= 8:\n            # compute cycles (only works for ampere GPUs)\n            mmas = BLOCK_M * BLOCK_N * BLOCK_K / (16 * 8 * 16)\n            mma_cycles = mmas / min(4, num_warps) * 8\n\n            ldgsts_latency = 300  # Does this matter?\n            optimal_num_stages = ldgsts_latency / mma_cycles\n\n            # nearest stages, prefer large #stages\n            nearest = heapq.nsmallest(\n                2,\n                v,\n                key=lambda x: (\n                    10 + abs(x[1] - optimal_num_stages)\n                    if (x[1] - optimal_num_stages) < 0\n                    else x[1] - optimal_num_stages\n                ),\n            )\n\n            for n in nearest:\n                pruned_configs.append(n[0])\n        else:  # Volta & Turing only supports num_stages <= 2\n            random_config = v[0][0]\n            random_config.num_stages = 2\n            pruned_configs.append(random_config)\n    return pruned_configs\n"
  },
  {
    "path": "xformers/ops/_triton/rmsnorm_kernels.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nimport torch\nimport triton\nimport triton.language as tl\n\nfrom xformers.triton.importing import libdevice_find\n\nrsqrt = libdevice_find(\"rsqrt\")\n\n\n@triton.jit\ndef _rms_norm_kernel(\n    x_ptr,\n    h1_ptr,\n    w_ptr,\n    eps,\n    stride,\n    N_COLS: tl.constexpr,\n    BLOCK_SIZE: tl.constexpr,\n    INCLUDE_WEIGHT: tl.constexpr,\n):\n    row = tl.program_id(0).to(tl.int64)\n    x_ptr += row * stride\n    h1_ptr += row * stride\n\n    _mean = tl.zeros([BLOCK_SIZE], dtype=tl.float32)\n    for offset in range(0, N_COLS, BLOCK_SIZE):\n        cols = offset + tl.arange(0, BLOCK_SIZE)\n        a = tl.load(\n            x_ptr + cols, mask=cols < N_COLS, other=0.0, eviction_policy=\"evict_last\"\n        ).to(tl.float32)\n        _mean += a * a\n    rstd = rsqrt((tl.sum(_mean, axis=0) / N_COLS) + eps)\n    for offset in range(0, N_COLS, BLOCK_SIZE):\n        cols = offset + tl.arange(0, BLOCK_SIZE)\n        mask = cols < N_COLS\n        a = tl.load(\n            x_ptr + cols, mask=mask, other=0.0, eviction_policy=\"evict_first\"\n        ).to(tl.float32)\n        if INCLUDE_WEIGHT:\n            w = tl.load(w_ptr + cols, mask=mask)\n            tl.store(h1_ptr + cols, a * rstd * w, mask=mask)\n        else:\n            tl.store(h1_ptr + cols, a * rstd, mask=mask)\n\n\n@triton.jit\ndef _rms_norm_add_kernel(\n    x_ptr,\n    y_ptr,\n    h1_ptr,\n    w_ptr,\n    eps,\n    stride,\n    N_COLS: tl.constexpr,\n    BLOCK_SIZE: tl.constexpr,\n    INCLUDE_WEIGHT: tl.constexpr,\n):\n    row = tl.program_id(0)\n    x_ptr += row * stride\n    y_ptr += row * stride\n    h1_ptr += row * stride\n\n    _mean = tl.zeros([BLOCK_SIZE], dtype=tl.float32)\n    for offset in range(0, N_COLS, BLOCK_SIZE):\n        cols = offset + tl.arange(0, BLOCK_SIZE)\n        mask = cols < N_COLS\n        ax = tl.load(\n            x_ptr + cols, mask=mask, other=0.0, eviction_policy=\"evict_last\"\n        ).to(tl.float32)\n        ay = tl.load(\n            y_ptr + cols, mask=mask, other=0.0, eviction_policy=\"evict_first\"\n        ).to(tl.float32)\n        a = ax + ay\n        tl.store(x_ptr + cols, a, mask=mask)\n        _mean += a * a\n    rstd = rsqrt((tl.sum(_mean, axis=0) / N_COLS) + eps)\n    for offset in range(0, N_COLS, BLOCK_SIZE):\n        cols = offset + tl.arange(0, BLOCK_SIZE)\n        mask = cols < N_COLS\n        a = tl.load(\n            x_ptr + cols, mask=mask, other=0.0, eviction_policy=\"evict_first\"\n        ).to(tl.float32)\n        if INCLUDE_WEIGHT:\n            w = tl.load(w_ptr + cols, mask=mask)\n            tl.store(h1_ptr + cols, a * rstd * w, mask=mask)\n        else:\n            tl.store(h1_ptr + cols, a * rstd, mask=mask)\n\n\ndef _rms_norm_forward(x, attn_norm_weights, eps):\n    if not x.is_contiguous():\n        raise ValueError(\"data must be contiguous\")\n    if attn_norm_weights is not None:\n        if not attn_norm_weights.is_contiguous():\n            raise ValueError(\"weights must be contiguous\")\n    out = torch.empty_like(x)\n    x_arg = x.reshape(-1, x.shape[-1])\n    M, N = x_arg.shape\n    # Less than 64KB per feature: enqueue fused kernel\n    MAX_FUSED_SIZE = 65536 // x.element_size()\n    BLOCK_SIZE = min(MAX_FUSED_SIZE, triton.next_power_of_2(N))\n    BLOCK_SIZE = max(BLOCK_SIZE, 128)\n    BLOCK_SIZE = min(BLOCK_SIZE, 8192)\n    # heuristics for number of warps\n    num_warps = min(max(BLOCK_SIZE // 256, 1), 8)\n    with torch.cuda.device(x.device):\n        _rms_norm_kernel[(M,)](\n            x_arg,\n            out,\n            attn_norm_weights,\n            eps,\n            x_arg.stride(0),\n            N,\n            BLOCK_SIZE=BLOCK_SIZE,\n            num_warps=num_warps,\n            INCLUDE_WEIGHT=attn_norm_weights is not None,\n        )\n    return out\n\n\ndef _rms_norm_add_forward(x, y, attn_norm_weights, eps):\n    # x, y contiguous of same shape [..., n]\n    # output of same shape, normed over the last dim.\n    if not x.is_contiguous():\n        raise ValueError(\"x must be contiguous\")\n    if not y.is_contiguous():\n        raise ValueError(\"y must be contiguous\")\n    if attn_norm_weights is not None:\n        if not attn_norm_weights.is_contiguous():\n            raise ValueError(\"weights must be contiguous\")\n    out = torch.empty_like(x)\n    x_arg = x.reshape(-1, x.shape[-1])\n    y_arg = y.reshape(-1, x.shape[-1])\n    M, N = x_arg.shape\n    # Less than 64KB per feature: enqueue fused kernel\n    MAX_FUSED_SIZE = 65536 // x.element_size()\n    BLOCK_SIZE = min(MAX_FUSED_SIZE, triton.next_power_of_2(N))\n    BLOCK_SIZE = max(BLOCK_SIZE, 128)\n    BLOCK_SIZE = min(BLOCK_SIZE, 8192)\n    # heuristics for number of warps\n    num_warps = min(max(BLOCK_SIZE // 256, 1), 8)\n    with torch.cuda.device(x.device):\n        _rms_norm_add_kernel[(M,)](\n            x_arg,\n            y_arg,\n            out,\n            attn_norm_weights,\n            eps,\n            x_arg.stride(0),\n            N,\n            BLOCK_SIZE=BLOCK_SIZE,\n            num_warps=num_warps,\n            INCLUDE_WEIGHT=attn_norm_weights is not None,\n        )\n    return out\n"
  },
  {
    "path": "xformers/ops/_triton/rope_padded_kernels.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nimport triton  # type: ignore\nimport triton.language as tl  # type: ignore\n\nfrom xformers.triton.importing import libdevice_find\n\npow = libdevice_find(\"pow\")\n\n\n@triton.jit\ndef _rope_padded_kernel(\n    xq,\n    xk,\n    xv,\n    out_q,\n    cache_k,\n    cache_v,\n    seqstartq,\n    seqstartk,\n    seqlenk,\n    theta,\n    linear_scale,\n    use_dynamic_scaling: tl.constexpr,\n    dynamic_old_context_len: tl.constexpr,\n    dynamic_scale_factor: tl.constexpr,\n    dynamic_low_freq_factor: tl.constexpr,\n    dynamic_high_freq_factor: tl.constexpr,\n    first_seqpos,\n    seqpos,\n    k_start: tl.constexpr,\n    v_start: tl.constexpr,\n    n_groups,\n    dim: tl.constexpr,  # dimension of each head\n    stride_xqM,\n    stride_xqG,\n    stride_xqH,\n    stride_xkM,\n    stride_xkG,\n    stride_xkH,\n    stride_xvM,\n    stride_xvG,\n    stride_xvH,\n    stride_cachekM,\n    stride_cachekG,\n    stride_cachekH,\n    stride_cachevM,\n    stride_cachevG,\n    stride_cachevH,\n    stride_seqstartq,\n    stride_seqstartk,\n    stride_seqlenk,\n    stride_outqM,\n    stride_outqG,\n    stride_outqH,\n    stride_seqpos,\n    internal_dtype: tl.constexpr,\n    # If True, seqstartq and seqstartk are not used but rather we\n    # assume that every batch element has the same number of\n    # queries (i.e. num_queries := tl.num_programs(1) )\n    # and the same cache space cache_padding_length.\n    # Always False when called below.\n    const_batch_strides: tl.constexpr,\n    # If const_batch_strides==True, the common cache length for each batch element.\n    # (Only the first seqlenk[i] elements are actually in use, and only the last\n    #  num_queries of those are actually written to.)\n    cache_padding_length,\n    # offset added to all values in seqlenk before using them.\n    # Always 0 when called below.\n    seqlenk_shift: tl.constexpr,\n    BLOCK_SIZE: tl.constexpr,\n    adjacents: tl.constexpr,\n):\n    \"\"\"\n    Each letter in this diagram is a whole row of length dim.\n\n     INPUT      xq        xk       xv\n\n        head_dim ─►\n\n      batch   qqqqqq      kk       vv\n        │     qqqqqq      kk       vv\n        ▼     qqqqqq      kk       vv\n\n    head_idx:  (goes across all heads of all 3 inputs)\n              ▲     ▲     ▲ ▲      ▲ ▲\n              │     │     │ │      │ │\n                          │        │\n              0  k_start  │v_start │n_total_heads\n                          │        │\n                          │        │\n                      k_start    v_start\n\n    Output is to out_q (same shape as xq), an xk-shaped part\n    of cache_k and an xv-shaped part of cache_v\n    \"\"\"\n    query_pos_in_batch_elt = tl.program_id(0)\n    batch_elt = tl.program_id(1)\n    group_head_idx = tl.program_id(2)\n    group_idx = group_head_idx % n_groups\n    head_idx = group_head_idx // n_groups\n\n    if internal_dtype == \"f32\":\n        theta = theta.to(tl.float32)\n    elif internal_dtype == \"f64\":\n        theta = theta.to(tl.float64)\n\n    if const_batch_strides:\n        query_pos = query_pos_in_batch_elt + tl.num_programs(1) * batch_elt\n        end_query_pos = tl.num_programs(1) * (batch_elt + 1)\n    else:\n        query_pos = query_pos_in_batch_elt + tl.load(\n            seqstartq + batch_elt * stride_seqstartq\n        )\n        end_query_pos = tl.load(seqstartq + (batch_elt + 1) * stride_seqstartq)\n        if query_pos >= end_query_pos:\n            return\n\n    is_q = head_idx < k_start\n    is_v = head_idx >= v_start\n\n    xq += query_pos * stride_xqM + head_idx * stride_xqH + group_idx * stride_xqG\n    out_q += (\n        query_pos * stride_outqM + head_idx * stride_outqH + group_idx * stride_outqG\n    )\n\n    if const_batch_strides:\n        cache_start = cache_padding_length * batch_elt\n    else:\n        cache_start = tl.load(seqstartk + batch_elt * stride_seqstartk)\n    end_of_batch_elt_cache = (\n        cache_start + tl.load(seqlenk + batch_elt * stride_seqlenk) + seqlenk_shift\n    )\n\n    cache_pos = end_of_batch_elt_cache - (end_query_pos - query_pos)\n    if seqpos is not None:\n        seq_pos = tl.load(seqpos + query_pos * stride_seqpos)\n    else:\n        seq_pos = cache_pos - cache_start\n        if first_seqpos is not None:\n            seq_pos += tl.load(first_seqpos + batch_elt * stride_seqpos)\n    cache_k += (\n        (head_idx - k_start) * stride_cachekH\n        + cache_pos * stride_cachekM\n        + group_idx * stride_cachekG\n    )\n    xk += (\n        query_pos * stride_xkM\n        + (head_idx - k_start) * stride_xkH\n        + group_idx * stride_xkG\n    )\n    in_qk = tl.where(is_q, xq, xk)\n    out_qk = tl.where(is_q, out_q, cache_k)\n\n    cache_v += (\n        (head_idx - v_start) * stride_cachevH\n        + cache_pos * stride_cachevM\n        + group_idx * stride_cachevG\n    )\n    xv += (\n        query_pos * stride_xvM\n        + (head_idx - v_start) * stride_xvH\n        + group_idx * stride_xvG\n    )\n\n    out = tl.where(is_v, cache_v, out_qk)\n    x_in = tl.where(is_v, xv, in_qk)\n\n    for offset in range(0, dim // 2, BLOCK_SIZE // 2):\n        c = tl.arange(0, BLOCK_SIZE // 2)\n        powers = (offset + c) * 2.0\n        if internal_dtype == \"f64\":\n            powers = powers.to(tl.float64)\n        if adjacents:\n            cols_re = (offset + c) * 2\n            cols_im = cols_re + 1\n        else:\n            cols_re = offset + c\n            cols_im = cols_re + dim // 2\n\n        mask = cols_im < dim\n\n        re_x = tl.load(x_in + cols_re, mask=mask)\n        im_x = tl.load(x_in + cols_im, mask=mask)\n        # freqs = seq_pos / (theta ** (powers / dim))\n        freqs = pow(theta, powers / (-dim))\n\n        if use_dynamic_scaling:\n            lo_freq_wavelen = dynamic_old_context_len / dynamic_low_freq_factor\n            hi_freq_wavelen = dynamic_old_context_len / dynamic_high_freq_factor\n\n            wavelens = 6.28318530718 / freqs  # 2*pi\n            is_low_freq = wavelens > lo_freq_wavelen\n            freqs = tl.where(is_low_freq, freqs / dynamic_scale_factor, freqs)\n\n            is_mid_freq = hi_freq_wavelen < wavelens and wavelens <= lo_freq_wavelen\n\n            smooth = (dynamic_old_context_len / wavelens - dynamic_low_freq_factor) / (\n                dynamic_high_freq_factor - dynamic_low_freq_factor\n            )\n            freqs = tl.where(\n                is_mid_freq,\n                (1 - smooth) * freqs / dynamic_scale_factor + smooth * freqs,\n                freqs,\n            )\n\n        freqs = seq_pos * freqs / linear_scale\n        sines = tl.sin(freqs)\n        cosines = tl.cos(freqs)\n        re_out = re_x * cosines - im_x * sines\n        im_out = im_x * cosines + re_x * sines\n\n        re_out_ = tl.where(is_v, re_x, re_out)\n        im_out_ = tl.where(is_v, im_x, im_out)\n        if internal_dtype == \"f64\":\n            if re_x.dtype == tl.bfloat16:\n                # triton 2.0.0 crashes if you try to convert\n                # float64 directly to bfloat16, so make an intermediate step.\n                re_out_ = re_out_.to(tl.float32)\n                im_out_ = im_out_.to(tl.float32)\n        tl.store(out + cols_re, re_out_, mask=mask)\n        tl.store(out + cols_im, im_out_, mask=mask)\n"
  },
  {
    "path": "xformers/ops/_triton/tiled_matmul_kernels.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport itertools\nfrom typing import List, Tuple\n\nimport torch\nimport triton\nimport triton.language as tl\n\nfrom xformers.ops._triton.matmul_perf_model import (\n    early_config_prune,\n    estimate_matmul_time,\n)\n\n\ndef init_to_zero(*names):\n    def result(nargs):\n        for name in names:\n            nargs[name].zero_()\n\n    return result\n\n\ndef gen_config(\n    block_m: int,\n    block_n: int,\n    block_k: int,\n    stages: int,\n    warps: int,\n    split_k: int = 1,\n    group_m: int = 8,\n) -> triton.Config:\n    \"\"\"A more compact way to define a triton.Config, so it fits on one line\"\"\"\n\n    return triton.Config(\n        {\n            \"BLOCK_M\": block_m,\n            \"BLOCK_N\": block_n,\n            \"BLOCK_K\": block_k,\n            \"SPLIT_K\": split_k,\n            \"GROUP_M\": group_m,\n        },\n        num_stages=stages,\n        num_warps=warps,\n        pre_hook=(\n            init_to_zero(*[f\"C{i+1}{j+1}\" for i in range(3) for j in range(3)])\n            if split_k > 1\n            else init_to_zero()\n        ),\n    )\n\n\nBASIC_MATMUL_CONFIGS = [\n    gen_config(block_m=128, block_n=256, block_k=32, stages=3, warps=8),\n    gen_config(block_m=256, block_n=128, block_k=32, stages=3, warps=8),\n    gen_config(block_m=256, block_n=64, block_k=32, stages=4, warps=4),\n    gen_config(block_m=64, block_n=256, block_k=32, stages=4, warps=4),\n    gen_config(block_m=128, block_n=128, block_k=32, stages=4, warps=4),\n    gen_config(block_m=128, block_n=64, block_k=32, stages=4, warps=4),\n    gen_config(block_m=64, block_n=128, block_k=32, stages=4, warps=4),\n    gen_config(block_m=128, block_n=32, block_k=32, stages=4, warps=4),\n    gen_config(block_m=64, block_n=32, block_k=32, stages=5, warps=2),\n]\n\n\nINT8_MATMUL_CONFIGS = [\n    gen_config(block_m=128, block_n=256, block_k=128, stages=3, warps=8),\n    gen_config(block_m=256, block_n=128, block_k=128, stages=3, warps=8),\n    gen_config(block_m=256, block_n=64, block_k=128, stages=4, warps=4),\n    gen_config(block_m=64, block_n=256, block_k=128, stages=4, warps=4),\n    gen_config(block_m=128, block_n=128, block_k=128, stages=4, warps=4),\n    gen_config(block_m=128, block_n=64, block_k=64, stages=4, warps=4),\n    gen_config(block_m=64, block_n=128, block_k=64, stages=4, warps=4),\n    gen_config(block_m=128, block_n=32, block_k=64, stages=4, warps=4),\n    gen_config(block_m=64, block_n=32, block_k=64, stages=5, warps=2),\n]\n\n\nIO_BOUND_MATMUL_CONFIGS_STAGES = [2, 3, 4, 5, 6]\nIO_BOUND_MATMUL_CONFIGS_BLOCK_M = [16, 32]\nIO_BOUND_MATMUL_CONFIGS_BLOCK_K = [32, 64]\nIO_BOUND_MATMUL_CONFIGS_BLOCK_N = [32, 64, 128, 256]\nIO_BOUND_MATMUL_CONFIGS_SPLIT_K = [1, 2, 4, 8, 16]\n\n\nIO_BOUND_MATMUL_CONFIGS = [\n    gen_config(\n        block_m=block_m,\n        block_n=block_n,\n        block_k=block_k,\n        stages=stages,\n        warps=2 if block_n <= 64 else 4,\n        split_k=split_k,\n    )\n    for stages, block_m, block_k, block_n, split_k in itertools.product(\n        IO_BOUND_MATMUL_CONFIGS_STAGES,\n        IO_BOUND_MATMUL_CONFIGS_BLOCK_M,\n        IO_BOUND_MATMUL_CONFIGS_BLOCK_K,\n        IO_BOUND_MATMUL_CONFIGS_BLOCK_N,\n        IO_BOUND_MATMUL_CONFIGS_SPLIT_K,\n    )\n]\n\n\nTRITON_CONFIGS = BASIC_MATMUL_CONFIGS + INT8_MATMUL_CONFIGS + IO_BOUND_MATMUL_CONFIGS\n\n\ndef our_estimate_matmul_time(\n    A11, B11, C11, M1, M2, M3, N1, N2, N3, K1, K2, K3, **kwargs\n):\n    \"\"\"Call into Triton's upstream cost model, with the right args\n\n    The upstream function expects arguments to have certain names. Since we\n    renamed a few of them in our implementation, we rename them back.\n\n    At the time of writing (July 2023) the arguments that Triton expects are:\n    M, N, K, A, B, C, BLOCK_M, BLOCK_N, BLOCK_K, SPLIT_K, num_warps, num_stages.\n\n    \"\"\"\n    return estimate_matmul_time(\n        M=M1 + M2 + M3, N=N1 + N2 + N3, K=K1 + K2 + K3, A=A11, B=B11, C=C11, **kwargs\n    )\n\n\ndef our_early_config_prune(config, named_args, **kwargs):\n    new_named_args = named_args.copy()\n    new_named_args[\"M\"] = named_args[\"M1\"] + named_args[\"M2\"] + named_args[\"M3\"]\n    new_named_args[\"N\"] = named_args[\"N1\"] + named_args[\"N2\"] + named_args[\"N3\"]\n    new_named_args[\"K\"] = named_args[\"K1\"] + named_args[\"K2\"] + named_args[\"K3\"]\n    new_named_args[\"A\"] = named_args[\"A11\"]\n    new_named_args[\"B\"] = named_args[\"B11\"]\n    new_named_args[\"C\"] = named_args[\"C11\"]\n    return early_config_prune(config, new_named_args, **kwargs)\n\n\n@triton.autotune(\n    configs=TRITON_CONFIGS,\n    key=[\"M1\", \"M2\", \"M3\", \"N1\", \"N2\", \"N3\", \"K1\", \"K2\", \"K3\"],\n    prune_configs_by={\n        \"early_config_prune\": our_early_config_prune,\n        \"perf_model\": our_estimate_matmul_time,\n        \"top_k\": 10,\n    },\n)\n@triton.heuristics(\n    {\n        \"EVEN_K\": lambda args: all(\n            k % (args[\"BLOCK_K\"] * args[\"SPLIT_K\"]) == 0\n            for k in [args[\"K1\"], args[\"K2\"], args[\"K3\"]]\n        ),\n    }\n)\n@triton.jit()\ndef _xformers_tiled_matmul_kernel(\n    A11,\n    A12,\n    A13,\n    A21,\n    A22,\n    A23,\n    A31,\n    A32,\n    A33,\n    B11,\n    B12,\n    B13,\n    B21,\n    B22,\n    B23,\n    B31,\n    B32,\n    B33,\n    C11,\n    C12,\n    C13,\n    C21,\n    C22,\n    C23,\n    C31,\n    C32,\n    C33,\n    M1,\n    M2,\n    M3,\n    N1,\n    N2,\n    N3,\n    K1,\n    K2,\n    K3,\n    stride_am1,\n    stride_am2,\n    stride_am3,\n    stride_ak1,\n    stride_ak2,\n    stride_ak3,\n    stride_bk1,\n    stride_bk2,\n    stride_bk3,\n    stride_bn1,\n    stride_bn2,\n    stride_bn3,\n    stride_cm1,\n    stride_cm2,\n    stride_cm3,\n    stride_cn1,\n    stride_cn2,\n    stride_cn3,\n    BLOCK_M: tl.constexpr,  # DO NOT CHANGE NAME: MUST MATCH PERF MODEL\n    BLOCK_N: tl.constexpr,  # DO NOT CHANGE NAME: MUST MATCH PERF MODEL\n    BLOCK_K: tl.constexpr,  # DO NOT CHANGE NAME: MUST MATCH PERF MODEL\n    GROUP_M: tl.constexpr,\n    SPLIT_K: tl.constexpr,  # DO NOT CHANGE NAME: MUST MATCH PERF MODEL\n    EVEN_K: tl.constexpr,\n    ACC_TYPE: tl.constexpr,\n):\n    # matrix multiplication\n    pid = tl.program_id(0)\n    pid_k = tl.program_id(1)\n    grid_m1 = tl.cdiv(M1, BLOCK_M)\n    grid_m2 = tl.cdiv(M2, BLOCK_M)\n    grid_m3 = tl.cdiv(M3, BLOCK_M)\n    grid_n1 = tl.cdiv(N1, BLOCK_N)\n    grid_n2 = tl.cdiv(N2, BLOCK_N)\n    grid_n3 = tl.cdiv(N3, BLOCK_N)\n    grid_m = grid_m1 + grid_m2 + grid_m3\n    grid_n = grid_n1 + grid_n2 + grid_n3\n\n    # re-order program ID for better L2 performance\n    width = GROUP_M * grid_n\n    group_id = pid // width\n    group_size = min(grid_m - group_id * GROUP_M, GROUP_M)\n    pid_m = group_id * GROUP_M + (pid % group_size)\n    pid_n = (pid % width) // (group_size)\n\n    # We use tl.where to circumvent a regression in alignment auto-detection:\n    # https://github.com/openai/triton/issues/1784\n\n    A1 = tl.where(pid_m < grid_m1, A11, tl.where(pid_m < grid_m1 + grid_m2, A21, A31))\n    A2 = tl.where(pid_m < grid_m1, A12, tl.where(pid_m < grid_m1 + grid_m2, A22, A32))\n    A3 = tl.where(pid_m < grid_m1, A13, tl.where(pid_m < grid_m1 + grid_m2, A23, A33))\n    B1 = tl.where(pid_n < grid_n1, B11, tl.where(pid_n < grid_n1 + grid_n2, B12, B13))\n    B2 = tl.where(pid_n < grid_n1, B21, tl.where(pid_n < grid_n1 + grid_n2, B22, B23))\n    B3 = tl.where(pid_n < grid_n1, B31, tl.where(pid_n < grid_n1 + grid_n2, B32, B33))\n    C = tl.where(\n        pid_m < grid_m1,\n        tl.where(pid_n < grid_n1, C11, tl.where(pid_n < grid_n1 + grid_n2, C12, C13)),\n        tl.where(\n            pid_m < grid_m1 + grid_m2,\n            tl.where(\n                pid_n < grid_n1, C21, tl.where(pid_n < grid_n1 + grid_n2, C22, C23)\n            ),\n            tl.where(\n                pid_n < grid_n1, C31, tl.where(pid_n < grid_n1 + grid_n2, C32, C33)\n            ),\n        ),\n    )\n    M = tl.where(pid_m < grid_m1, M1, tl.where(pid_m < grid_m1 + grid_m2, M2, M3))\n    N = tl.where(pid_n < grid_n1, N1, tl.where(pid_n < grid_n1 + grid_n2, N2, N3))\n    stride_ak = tl.where(\n        pid_m < grid_m1,\n        stride_ak1,\n        tl.where(pid_m < grid_m1 + grid_m2, stride_ak2, stride_ak3),\n    )\n    stride_bk = tl.where(\n        pid_n < grid_n1,\n        stride_bk1,\n        tl.where(pid_n < grid_n1 + grid_n2, stride_bk2, stride_bk3),\n    )\n    stride_cn = tl.where(\n        pid_m < grid_m1,\n        stride_cn1,\n        tl.where(pid_m < grid_m1 + grid_m2, stride_cn2, stride_cn3),\n    )\n    stride_cm = tl.where(\n        pid_n < grid_n1,\n        stride_cm1,\n        tl.where(pid_n < grid_n1 + grid_n2, stride_cm2, stride_cm3),\n    )\n    pid_m = tl.where(\n        pid_m < grid_m1,\n        pid_m,\n        tl.where(pid_m < grid_m1 + grid_m2, pid_m - grid_m1, pid_m - grid_m1 - grid_m2),\n    )\n    pid_n = tl.where(\n        pid_n < grid_n1,\n        pid_n,\n        tl.where(pid_n < grid_n1 + grid_n2, pid_n - grid_n1, pid_n - grid_n1 - grid_n2),\n    )\n\n    # do matrix multiplication\n    rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)\n    rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)\n    ram = tl.max_contiguous(tl.multiple_of(rm % M, BLOCK_M), BLOCK_M)\n    rbn = tl.max_contiguous(tl.multiple_of(rn % N, BLOCK_N), BLOCK_N)\n    # pointers\n    acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=ACC_TYPE)\n    grid_k1 = tl.cdiv(K1, BLOCK_K)\n    grid_k2 = tl.cdiv(K2, BLOCK_K)\n    grid_k3 = tl.cdiv(K3, BLOCK_K)\n    for tile in range(pid_k, grid_k1 + grid_k2 + grid_k3, SPLIT_K):\n        A = tl.where(tile < grid_k1, A1, tl.where(tile < grid_k1 + grid_k2, A2, A3))\n        B = tl.where(tile < grid_k1, B1, tl.where(tile < grid_k1 + grid_k2, B2, B3))\n        K = tl.where(tile < grid_k1, K1, tl.where(tile < grid_k1 + grid_k2, K2, K3))\n        stride_am = tl.where(\n            tile < grid_k1,\n            stride_am1,\n            tl.where(tile < grid_k1 + grid_k2, stride_am2, stride_am3),\n        )\n        stride_bn = tl.where(\n            tile < grid_k1,\n            stride_bn1,\n            tl.where(tile < grid_k1 + grid_k2, stride_bn2, stride_bn3),\n        )\n        my_tile = tl.where(\n            tile < grid_k1,\n            tile,\n            tl.where(\n                tile < grid_k1 + grid_k2, tile - grid_k1, tile - grid_k1 - grid_k2\n            ),\n        )\n        rk = my_tile * BLOCK_K + tl.arange(0, BLOCK_K)\n        Ain = A + (ram[:, None] * stride_am + rk[None, :] * stride_ak)\n        Bin = B + (rk[:, None] * stride_bk + rbn[None, :] * stride_bn)\n        if EVEN_K:\n            a = tl.load(Ain)\n            b = tl.load(Bin)\n        else:\n            a = tl.load(Ain, mask=rk[None, :] < K, other=0.0)\n            b = tl.load(Bin, mask=rk[:, None] < K, other=0.0)\n        acc += tl.dot(a, b, allow_tf32=False)\n    acc = acc.to(C.dtype.element_ty)\n    # rematerialize rm and rn to save registers\n    rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)\n    rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)\n    C = C + (rm[:, None] * stride_cm + rn[None, :] * stride_cn)\n    mask = (rm < M)[:, None] & (rn < N)[None, :]\n    # handles write-back with reduction-splitting\n    if SPLIT_K == 1:\n        tl.store(C, acc, mask=mask)\n    else:\n        tl.atomic_add(C, acc, mask=mask)\n\n\ndef _check_row_or_column(row_or_col_type, row_or_col_idx, tensor_name, dim_name, vals):\n    assert len(vals) > 0\n    for pos, val in enumerate(vals[1:]):\n        assert val == vals[0], (\n            f\"the tensors on {row_or_col_type} {row_or_col_idx} of the {tensor_name} \"\n            f\"must all have the same stride along the {dim_name} dimension, got \"\n            f\"{vals[0]} at position 0 and {val} at position {pos + 1}\"\n        )\n    return vals[0]\n\n\ndef _get_strides(\n    ts: List[List[torch.Tensor]], tensor_name, dim_0_name, dim_1_name\n) -> Tuple[List[int], List[int]]:\n    strides_0 = [\n        _check_row_or_column(\n            \"column\", idx, tensor_name, dim_0_name, [y.stride(0) for y in x]\n        )\n        for idx, x in enumerate(zip(*ts))\n    ]\n    strides_1 = [\n        _check_row_or_column(\n            \"row\", idx, tensor_name, dim_1_name, [y.stride(1) for y in x]\n        )\n        for idx, x in enumerate(ts)\n    ]\n    assert all(s == 1 for s in strides_0) or all(s == 1 for s in strides_1)\n    while len(strides_0) < 3:\n        strides_0.append(1 if strides_0[0] == 1 else 0)\n    while len(strides_1) < 3:\n        strides_1.append(1 if strides_1[0] == 1 else 0)\n    return strides_0, strides_1\n\n\ndef _launch_triton_matmul(\n    a: List[List[torch.Tensor]],\n    b: List[List[torch.Tensor]],\n    c: List[List[torch.Tensor]],\n    ms: List[int],\n    ns: List[int],\n    ks: List[int],\n) -> None:\n    strides_am, strides_ak = _get_strides(a, \"first operand\", \"m\", \"k\")\n    strides_bk, strides_bn = _get_strides(b, \"second operand\", \"k\", \"n\")\n    strides_cm, strides_cn = _get_strides(c, \"output\", \"m\", \"n\")\n\n    # accumulator types\n    ACC_TYPE = (\n        tl.float32\n        if c[0][0].dtype in [torch.float16, torch.bfloat16, torch.float32]\n        else tl.int32\n    )\n\n    # launch kernel\n    def grid(META):\n        return (\n            sum(triton.cdiv(m, META[\"BLOCK_M\"]) for m in ms)\n            * sum(triton.cdiv(n, META[\"BLOCK_N\"]) for n in ns),\n            META[\"SPLIT_K\"],\n        )\n\n    _xformers_tiled_matmul_kernel[grid](\n        *[\n            a[min(i, len(a) - 1)][min(j, len(a[0]) - 1)]\n            for i in range(3)\n            for j in range(3)\n        ],\n        *[\n            b[min(i, len(b) - 1)][min(j, len(b[0]) - 1)]\n            for i in range(3)\n            for j in range(3)\n        ],\n        *[\n            c[min(i, len(c) - 1)][min(j, len(c[0]) - 1)]\n            for i in range(3)\n            for j in range(3)\n        ],\n        *[ms[i] if len(ms) > i else 0 for i in range(3)],\n        *[ns[i] if len(ns) > i else 0 for i in range(3)],\n        *[ks[i] if len(ks) > i else 0 for i in range(3)],\n        *strides_am,\n        *strides_ak,\n        *strides_bk,\n        *strides_bn,\n        *strides_cm,\n        *strides_cn,\n        ACC_TYPE=ACC_TYPE,\n    )\n"
  },
  {
    "path": "xformers/ops/common.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, Dict, List, Type, TypeVar\n\nimport torch\n\n\ndef get_operator(library: str, name: str):\n    def no_such_operator(*args, **kwargs):\n        raise RuntimeError(\n            f\"No such operator {library}::{name} - did you forget to build xformers with `python setup.py develop`?\"\n        )\n\n    try:\n        return getattr(getattr(torch.ops, library), name)\n    except (RuntimeError, AttributeError):\n        return no_such_operator\n\n\ndef get_xformers_operator(name: str):\n    return get_operator(\"xformers\", name)\n\n\nclass BaseOperator:\n    OPERATOR: Any\n    NAME: str\n    OPERATOR_CATEGORY: str\n\n    @classmethod\n    def is_available(cls) -> bool:\n        # cls.OPERATOR can be either a kernel or a Triton Autotuner object, which doesn't have __name__\n        if (\n            cls.OPERATOR is None\n            or getattr(cls.OPERATOR, \"__name__\", \"\") == \"no_such_operator\"\n        ):\n            return False\n        return True\n\n\nOPERATORS_REGISTRY: List[Type[BaseOperator]] = []\nFUNC_TO_XFORMERS_OPERATOR: Dict[Any, Type[BaseOperator]] = {}\n\nClsT = TypeVar(\"ClsT\")\n\n\ndef register_operator(cls: ClsT) -> ClsT:\n    global OPERATORS_REGISTRY, FUNC_TO_XFORMERS_OPERATOR\n    OPERATORS_REGISTRY.append(cls)  # type: ignore\n    FUNC_TO_XFORMERS_OPERATOR[cls.OPERATOR] = cls  # type: ignore\n    return cls\n\n\n# post-2.0, avoids a warning\n# (`torch.Tensor.storage` will also be deleted in the future)\n_GET_TENSOR_STORAGE = getattr(torch.Tensor, \"untyped_storage\", None)\nif _GET_TENSOR_STORAGE is None:  # pre-2.0, `untyped_storage` didn't exist\n    _GET_TENSOR_STORAGE = torch.Tensor.storage\n\n\ndef _get_storage_base(x: torch.Tensor) -> int:\n    return _GET_TENSOR_STORAGE(x).data_ptr()  # type: ignore\n"
  },
  {
    "path": "xformers/ops/differentiable_collectives.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Optional, Tuple\n\nimport torch\nimport torch.distributed\n\n\ndef all_reduce(\n    x: torch.Tensor, *, process_group: torch.distributed.ProcessGroup\n) -> None:\n    mp_size = process_group.size()\n    if mp_size == 1:\n        return\n\n    torch.distributed.all_reduce(\n        tensor=x, op=torch.distributed.ReduceOp.SUM, group=process_group\n    )\n\n\ndef gather_along_first_dim_async(\n    input_: torch.Tensor, *, process_group: torch.distributed.ProcessGroup\n) -> Tuple[torch.Tensor, Optional[torch.distributed.Work]]:\n    mp_size = process_group.size()\n    if mp_size == 1:\n        return input_, None\n\n    output = input_.new_empty((input_.shape[0] * mp_size,) + input_.shape[1:])\n    handle = torch.distributed.all_gather_into_tensor(\n        output_tensor=output,\n        input_tensor=input_,\n        group=process_group,\n        async_op=True,\n    )\n\n    return output, handle\n\n\ndef reduce_scatter_along_first_dim_async(\n    input_: torch.Tensor, *, process_group: torch.distributed.ProcessGroup\n) -> Tuple[torch.Tensor, Optional[torch.distributed.Work]]:\n    mp_size = process_group.size()\n    if mp_size == 1:\n        return input_, None\n\n    assert input_.shape[0] % mp_size == 0\n    output = input_.new_empty((input_.shape[0] // mp_size,) + input_.shape[1:])\n    handle = torch.distributed.reduce_scatter_tensor(\n        output=output,\n        input=input_,\n        op=torch.distributed.ReduceOp.SUM,\n        group=process_group,\n        async_op=True,\n    )\n\n    return output, handle\n\n\ndef gather_along_first_dim(\n    input_: torch.Tensor, *, process_group: torch.distributed.ProcessGroup\n) -> torch.Tensor:\n    output, handle = gather_along_first_dim_async(input_, process_group=process_group)\n    if handle is not None:\n        handle.wait()\n    return output\n\n\ndef reduce_scatter_along_first_dim(\n    input_: torch.Tensor, *, process_group: torch.distributed.ProcessGroup\n) -> torch.Tensor:\n    output, handle = reduce_scatter_along_first_dim_async(\n        input_, process_group=process_group\n    )\n    if handle is not None:\n        handle.wait()\n    return output\n\n\nclass _CopyToModelParallelRegion(torch.autograd.Function):\n    @staticmethod\n    def forward(  # type: ignore[override]\n        ctx, input_: torch.Tensor, process_group: torch.distributed.ProcessGroup\n    ) -> torch.Tensor:\n        ctx.process_group = process_group\n        return input_\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx, grad_output: torch.Tensor\n    ) -> Tuple[torch.Tensor, None]:\n        all_reduce(grad_output, process_group=ctx.process_group)\n        return grad_output, None\n\n\ndef copy_to_model_parallel_region(\n    x: torch.Tensor, process_group: Optional[torch.distributed.ProcessGroup]\n) -> torch.Tensor:\n    if process_group is None:\n        return x\n    return _CopyToModelParallelRegion.apply(x, process_group)\n\n\nclass _ReduceFromModelParallelRegion(torch.autograd.Function):\n    @staticmethod\n    def forward(  # type: ignore[override]\n        ctx, input_: torch.Tensor, process_group: torch.distributed.ProcessGroup\n    ) -> torch.Tensor:\n        all_reduce(input_, process_group=process_group)\n        ctx.mark_dirty(input_)\n        return input_\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx, grad_output: torch.Tensor\n    ) -> Tuple[torch.Tensor, None]:\n        return grad_output, None\n\n\ndef reduce_from_model_parallel_region(\n    x: torch.Tensor, process_group: Optional[torch.distributed.ProcessGroup]\n) -> torch.Tensor:\n    if process_group is None:\n        return x\n    return _ReduceFromModelParallelRegion.apply(x, process_group)\n\n\nclass _GatherFromSequenceParallelRegion(torch.autograd.Function):\n    @staticmethod\n    def forward(  # type: ignore[override]\n        ctx, x: torch.Tensor, process_group: torch.distributed.ProcessGroup\n    ) -> torch.Tensor:\n        ctx.process_group = process_group\n        return gather_along_first_dim(x, process_group=process_group)\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx, grad_output: torch.Tensor\n    ) -> Tuple[torch.Tensor, None]:\n        return (\n            reduce_scatter_along_first_dim(\n                grad_output, process_group=ctx.process_group\n            ),\n            None,\n        )\n\n\ndef gather_from_sequence_parallel_region(\n    x: torch.Tensor, process_group: Optional[torch.distributed.ProcessGroup]\n) -> torch.Tensor:\n    if process_group is None:\n        return x\n    return _GatherFromSequenceParallelRegion.apply(x, process_group)\n\n\nclass _ScatterToSequenceParallelRegion(torch.autograd.Function):\n    @staticmethod\n    def forward(  # type: ignore[override]\n        ctx, x: torch.Tensor, process_group: torch.distributed.ProcessGroup\n    ) -> torch.Tensor:\n        ctx.process_group = process_group\n        return reduce_scatter_along_first_dim(x, process_group=process_group)\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx, grad_output: torch.Tensor\n    ) -> Tuple[torch.Tensor, None]:\n        return (\n            gather_along_first_dim(grad_output, process_group=ctx.process_group),\n            None,\n        )\n\n\ndef scatter_to_sequence_parallel_region(\n    x: torch.Tensor, process_group: Optional[torch.distributed.ProcessGroup]\n) -> torch.Tensor:\n    if process_group is None:\n        return x\n    return _ScatterToSequenceParallelRegion.apply(x, process_group)\n"
  },
  {
    "path": "xformers/ops/fmha/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, cast, List, Optional, Sequence, Tuple, Type, Union\n\nimport torch\n\nfrom . import (\n    attn_bias,\n    ck,\n    ck_splitk,\n    cutlass,\n    cutlass_blackwell,\n    flash,\n    flash3,\n    triton_splitk,\n)\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalMask,\n    LowerTriangularMask,\n    VARLEN_BIASES,\n)\nfrom .common import (\n    AttentionBwOpBase,\n    AttentionFwOpBase,\n    AttentionOp,\n    AttentionOpBase,\n    bmk2bmhk,\n    Context,\n    Gradients,\n    Inputs,\n)\nfrom .dispatch import (\n    _dispatch_bw,\n    _dispatch_fw,\n    _ensure_op_supports_or_raise,\n    _get_use_fa3,\n    _set_use_fa3,\n)\n\nMemoryEfficientAttentionCutlassOp = (cutlass.FwOp, cutlass.BwOp)\nMemoryEfficientAttentionCutlassBlackwellOp = (\n    cutlass_blackwell.FwOp,\n    cutlass_blackwell.BwOp,\n)\nMemoryEfficientAttentionCutlassFwdFlashBwOp = (cutlass.FwOp, flash.BwOp)\nMemoryEfficientAttentionFlashAttentionOp = (flash.FwOp, flash.BwOp)\nMemoryEfficientAttentionCkOp = (ck.FwOp, ck.BwOp)\nMemoryEfficientAttentionSplitKCkOp = (ck_splitk.FwOp, ck.BwOp)\n\n\ndef _deserialize_bias(attn_bias_ctx, attn_bias_tensor: Optional[torch.Tensor]) -> Any:\n    if attn_bias_tensor is None:\n        return attn_bias_ctx\n    return attn_bias_tensor\n\n\n# Note: `torch.compile` only allows custom autograd functions\n# to accept a subset of types. Therefore we serialize `op` objects\n# to `str` before entering the function, and unserialize them inside.\n# See also: https://github.com/pytorch/pytorch/issues/118395\n_OPS_LOOKUP = {\n    flash.FwOp.NAME: flash.FwOp,\n    flash.BwOp.NAME: flash.BwOp,\n}\n\n\ndef _serialize_op(op):\n    if op is not None and op.NAME in _OPS_LOOKUP:\n        return op.NAME\n    return op\n\n\ndef _unserialize_op(op):\n    if isinstance(op, str):\n        return _OPS_LOOKUP[op]\n    return op\n\n\nclass _fMHA(torch.autograd.Function):\n    @staticmethod\n    # type: ignore\n    def forward(ctx, op_fw, op_bw, *args: Any) -> Any:\n        inp = Inputs(*args)\n\n        op_fw = _unserialize_op(op_fw)\n        op_bw = _unserialize_op(op_bw)\n\n        out, op_ctx = _memory_efficient_attention_forward_requires_grad(\n            inp=inp, op=op_fw\n        )\n\n        # Saving attn_bias is a bit complicated, as the\n        # torch part should go in `save_for_backward`\n        if isinstance(inp.attn_bias, torch.Tensor):\n            attn_bias_tensor = inp.attn_bias\n            attn_bias_ctx = None\n        else:\n            attn_bias_tensor = None\n            attn_bias_ctx = inp.attn_bias\n\n        ctx.save_for_backward(\n            inp.query,\n            inp.key,\n            inp.value,\n            op_ctx.out,\n            op_ctx.lse,\n        )\n        ctx.rng_state = op_ctx.rng_state\n        ctx.attn_bias_tensor = attn_bias_tensor\n        if op_ctx.op_bw is not None:\n            if op_bw is not None and op_bw is not op_ctx.op_bw:\n                raise ValueError(\n                    f\"Specified op_bw={op_bw.NAME}, but forward op \"\n                    f\"can only run with op_bw={op_ctx.op_bw.NAME}. Please set op_bw=None.\"\n                )\n            op_bw = op_ctx.op_bw\n        if (\n            op_fw is not None\n            and op_bw is not None\n            and isinstance(inp.attn_bias, VARLEN_BIASES)\n            and inp.attn_bias.q_seqinfo.seqstart.shape[0] > 2\n            and op_bw.VARLEN_LSE_PACKED != op_fw.VARLEN_LSE_PACKED\n        ):\n            raise ValueError(\n                f\"Specified op_bw={op_bw.NAME} is not compatible with the \"\n                f\"op_fw={op_fw.NAME}, because they use different format of logsumexp. \"\n                f\"NOTE: This is new with xFormers 0.0.28\"\n            )\n        if op_bw is None and (\n            inp.query.requires_grad or inp.key.requires_grad or inp.value.requires_grad\n        ):\n            varlen_lse_packed = _detect_lse_packed_or_raise(op_ctx.lse, inp)\n            if varlen_lse_packed is not None and op_fw is not None:\n                assert (\n                    op_fw.VARLEN_LSE_PACKED == varlen_lse_packed\n                ), f\"{op_fw.NAME}: wrong value for `VARLEN_LSE_PACKED` ?\"\n            # NOTE: We need to check tensor strides to decide which operator we run in the BW pass.\n            # Unfortunately, PyTorch only allows to call this function during the FW pass, so\n            # we decide the operator to use now.\n            op_bw = _dispatch_bw(inp, varlen_lse_packed=varlen_lse_packed)\n        ctx.op_fw = op_fw\n        ctx.op_bw = op_bw\n        ctx.p = inp.p\n        # This allows to create gradients from a single storage,\n        # to avoid a \"cat\" in the BW pass.\n        # The heuristic is approximative, but:\n        # (1) It's not a big issue to create a shared storage\n        # (2) The heuristic needs to pass `torch.compile`\n        #  (this is also why we run it in the FW pass, the BW pass is stricter)\n        ctx.qkv_share_storage = (\n            inp.query.shape[0] == inp.key.shape[0]\n            and inp.query.shape[-1] == inp.value.shape[-1]\n            and inp.query.stride(-2)\n            == (inp.key.shape[-1] + inp.query.shape[-1] + inp.value.shape[-1])\n        )\n\n        ctx.scale = inp.scale\n        ctx.attn_bias_ctx = attn_bias_ctx\n        ctx.n_args = len(args)\n        return out, op_ctx.lse\n\n    @staticmethod\n    @torch.autograd.function.once_differentiable\n    def backward(ctx, grad, grad_lse):\n        # Re-create context\n        query, key, value, out, lse = ctx.saved_tensors\n        attn_bias_tensor = ctx.attn_bias_tensor\n        rng_state = ctx.rng_state\n        inp = Inputs(\n            query=query,\n            key=key,\n            value=value,\n            attn_bias=_deserialize_bias(ctx.attn_bias_ctx, attn_bias_tensor),\n            p=ctx.p,\n            scale=ctx.scale,\n        )\n        op_ctx = Context(\n            lse=lse,\n            out=out,\n            rng_state=rng_state,\n            qkv_share_storage=ctx.qkv_share_storage,\n        )\n        grads = _memory_efficient_attention_backward(\n            ctx=op_ctx,\n            inp=inp,\n            grad=grad,\n            op=ctx.op_bw,\n            _skip_op_checks=True,\n        )\n        return (None, None, grads.dq, grads.dk, grads.dv, grads.db) + (None,) * (\n            ctx.n_args - 2\n        )\n\n\ndef memory_efficient_attention(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[AttentionOp] = None,\n    output_dtype: Optional[torch.dtype] = None,\n) -> torch.Tensor:\n    \"\"\"Implements the memory-efficient attention mechanism following\n    `\"Self-Attention Does Not Need O(n^2) Memory\" <http://arxiv.org/abs/2112.05682>`_.\n\n    :Inputs shape:\n\n    - Input tensors must be in format ``[B, M, H, K]``, where B is the batch size, M \\\n        the sequence length, H the number of heads, and K the embeding size per head\n\n    - If inputs have dimension 3, it is assumed that the dimensions are ``[B, M, K]`` and ``H=1``\n\n    - Inputs can also be of dimension 5 with GQA - see note below\n\n    - Inputs can be non-contiguous - we only require the last dimension's stride to be 1\n\n\n    :Equivalent pytorch code:\n\n    .. code-block:: python\n\n        scale = 1.0 / query.shape[-1] ** 0.5\n        query = query * scale\n        query = query.transpose(1, 2)\n        key = key.transpose(1, 2)\n        value = value.transpose(1, 2)\n        attn = query @ key.transpose(-2, -1)\n        if attn_bias is not None:\n            attn = attn + attn_bias\n        attn = attn.softmax(-1)\n        attn = F.dropout(attn, p)\n        attn = attn @ value\n        return attn.transpose(1, 2).contiguous()\n\n    :Examples:\n\n    .. code-block:: python\n\n        import xformers.ops as xops\n\n        # Compute regular attention\n        y = xops.memory_efficient_attention(q, k, v)\n\n        # With a dropout of 0.2\n        y = xops.memory_efficient_attention(q, k, v, p=0.2)\n\n        # Causal attention\n        y = xops.memory_efficient_attention(\n            q, k, v,\n            attn_bias=xops.LowerTriangularMask()\n        )\n\n    :Supported hardware:\n\n        NVIDIA GPUs with compute capability above 6.0 (P100+), datatype ``f16``, ``bf16`` and ``f32``.\n\n    :EXPERIMENTAL: Using with Multi Query Attention (MQA) and Grouped Query Attention (GQA):\n\n        MQA/GQA is an experimental feature supported only for the forward pass.\n        If you have 16 heads in query, and 2 in key/value, you can provide 5-dim tensors\n        in the ``[B, M, G, H, K]`` format, where ``G`` is the number of head groups (here 2), and\n        ``H`` is the number of heads per group (8 in the example).\n\n        Please note that xFormers will not automatically broadcast the inputs, so you will need\n        to broadcast it manually before calling `memory_efficient_attention`.\n\n    :GQA/MQA example:\n\n    .. code-block:: python\n\n        import torch\n        import xformers.ops as xops\n\n        B, M, K = 3, 32, 128\n        kwargs = dict(device=\"cuda\", dtype=torch.float16)\n        q = torch.randn([B, M, 8, K], **kwargs)\n        k = torch.randn([B, M, 2, K], **kwargs)\n        v = torch.randn([B, M, 2, K], **kwargs)\n        out_gqa = xops.memory_efficient_attention(\n            q.reshape([B, M, 2, 4, K]),\n            k.reshape([B, M, 2, 1, K]).expand([B, M, 2, 4, K]),\n            v.reshape([B, M, 2, 1, K]).expand([B, M, 2, 4, K]),\n        )\n\n    Raises:\n        NotImplementedError: if there is no operator available to compute the MHA\n        ValueError: if inputs are invalid\n\n    :parameter query: Tensor of shape ``[B, Mq, H, K]``\n    :parameter key: Tensor of shape ``[B, Mkv, H, K]``\n    :parameter value: Tensor of shape ``[B, Mkv, H, Kv]``\n    :parameter attn_bias: Bias to apply to the attention matrix - defaults to no masking. \\\n        For common biases implemented efficiently in xFormers, see :attr:`xformers.ops.fmha.attn_bias.AttentionBias`. \\\n        This can also be a :attr:`torch.Tensor` for an arbitrary mask (slower).\n    :parameter p: Dropout probability. Disabled if set to ``0.0``\n    :parameter scale: Scaling factor for ``Q @ K.transpose()``. If set to ``None``, the default \\\n        scale (q.shape[-1]**-0.5) will be used.\n    :parameter op: The operators to use - see :attr:`xformers.ops.AttentionOpBase`. \\\n        If set to ``None`` (recommended), xFormers \\\n        will dispatch to the best available operator, depending on the inputs \\\n        and options.\n    :return: multi-head attention Tensor with shape ``[B, Mq, H, Kv]``\n    \"\"\"\n    return _memory_efficient_attention(\n        Inputs(\n            query=query,\n            key=key,\n            value=value,\n            p=p,\n            attn_bias=attn_bias,\n            scale=scale,\n            output_dtype=output_dtype,\n        ),\n        op=op,\n    )\n\n\ntorch.library.define(\n    \"xformer::memory_efficient_attention_forward\",\n    \"(Tensor q, Tensor k, Tensor v, Tensor? b = None, float? p = 0.0, float? scale = None) -> Tensor\",\n)\n\n\n@torch.library.impl(\"xformer::memory_efficient_attention_forward\", \"Meta\")\ndef memory_efficient_attention_forward_meta(q, k, v):\n    return q.new_empty(q.shape)\n\n\n# torch.compile has issue when tracing through op dispatch and ensure_op_support\n# so provide a wrapper to register it as a custom torch library op.\n@torch.library.impl(\"xformer::memory_efficient_attention_forward\", \"CUDA\")\ndef memory_efficient_attention_forward_torch_wrapper(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n) -> torch.Tensor:\n    \"\"\"\n    This provides a torch-compilable wrapper op to\n    memory_efficient_attention_forward in certain special cases.\n\n    Note that the following are not supported\n        - `op` input (?)\n        - certain attn_bias types (?)\n        - output_dtype\n        - K != Kv\n    \"\"\"\n    return memory_efficient_attention_forward(\n        query,\n        key,\n        value,\n        attn_bias,\n        p,\n        scale,\n    )\n\n\ndef memory_efficient_attention_forward(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[Type[AttentionFwOpBase]] = None,\n    output_dtype: Optional[torch.dtype] = None,\n) -> torch.Tensor:\n    \"\"\"\n    Calculates the forward pass of :attr:`xformers.ops.memory_efficient_attention`.\n    \"\"\"\n    return _memory_efficient_attention_forward(\n        Inputs(\n            query=query,\n            key=key,\n            value=value,\n            p=p,\n            attn_bias=attn_bias,\n            scale=scale,\n            output_dtype=output_dtype,\n        ),\n        op=op,\n    )\n\n\ndef memory_efficient_attention_forward_requires_grad(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[Type[AttentionFwOpBase]] = None,\n    output_dtype: Optional[torch.dtype] = None,\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"\n    Returns a tuple (output, lse), where `lse` can be used to compute the backward pass later.\n    See :attr:`xformers.ops.memory_efficient_attention` for an explanation of the arguments\n    See :attr:`xformers.ops.memory_efficient_attention_backward` for running the backward pass\n    \"\"\"\n    if p != 0.0:\n        raise NotImplementedError(\n            \"dropout is not supported on the non-autograd API.\"\n            \" If you want to use dropout, please call `memory_efficient_attention` directly\"\n        )\n    out, ctx = _memory_efficient_attention_forward_requires_grad(\n        Inputs(\n            query=query,\n            key=key,\n            value=value,\n            p=p,\n            attn_bias=attn_bias,\n            scale=scale,\n            output_dtype=output_dtype,\n        ),\n        op=op,\n    )\n    return out, ctx.lse\n\n\ndef memory_efficient_attention_backward(\n    grad: torch.Tensor,\n    output: torch.Tensor,\n    lse: torch.Tensor,\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[Type[AttentionBwOpBase]] = None,\n) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n    \"\"\"\n    Computes the gradient of the attention.\n    Returns a tuple (dq, dk, dv)\n    See :attr:`xformers.ops.memory_efficient_attention` for an explanation of the arguments.\n    `lse` is the tensor returned by\n    :attr:`xformers.ops.memory_efficient_attention_forward_requires_grad`\n    \"\"\"\n    if p != 0.0:\n        raise NotImplementedError(\n            \"dropout is not supported on the non-autograd API.\"\n            \" If you want to use dropout, please call `memory_efficient_attention` directly\"\n        )\n    gradients = _memory_efficient_attention_backward(\n        Context(out=output, lse=lse),\n        Inputs(\n            query=query, key=key, value=value, p=p, attn_bias=attn_bias, scale=scale\n        ),\n        grad,\n        op=op,\n    )\n    return (gradients.dq, gradients.dk, gradients.dv)\n\n\ndef _memory_efficient_attention(\n    inp: Inputs, op: Optional[AttentionOp] = None\n) -> torch.Tensor:\n    # fast-path that doesn't require computing the logsumexp for backward computation\n    if all(x.requires_grad is False for x in [inp.query, inp.key, inp.value]):\n        return _memory_efficient_attention_forward(\n            inp, op=op[0] if op is not None else None\n        )\n\n    output_shape = inp.normalize_bmhk()\n\n    op_fw = _serialize_op(op[0] if op is not None else None)\n    op_bw = _serialize_op(op[1] if op is not None else None)\n    return _fMHA.apply(\n        op_fw, op_bw, inp.query, inp.key, inp.value, inp.attn_bias, inp.p, inp.scale\n    )[0].reshape(output_shape)\n\n\ndef _memory_efficient_attention_forward(\n    inp: Inputs, op: Optional[Type[AttentionFwOpBase]]\n) -> torch.Tensor:\n    inp.validate_inputs()\n    output_shape = inp.normalize_bmhk()\n    if op is None:\n        op = _dispatch_fw(inp, False)\n    else:\n        _ensure_op_supports_or_raise(ValueError, \"memory_efficient_attention\", op, inp)\n\n    out, *_ = op.apply(inp, needs_gradient=False)\n    return out.reshape(output_shape)\n\n\ndef _memory_efficient_attention_forward_requires_grad(\n    inp: Inputs, op: Optional[Type[AttentionFwOpBase]]\n) -> Tuple[torch.Tensor, Context]:\n    inp.validate_inputs()\n    output_shape = inp.normalize_bmhk()\n    if op is None:\n        op = _dispatch_fw(inp, True)\n    else:\n        _ensure_op_supports_or_raise(ValueError, \"memory_efficient_attention\", op, inp)\n    out = op.apply(inp, needs_gradient=True)\n    assert out[1] is not None\n    return (out[0].reshape(output_shape), out[1])\n\n\ndef _detect_lse_packed_or_raise(lse: torch.Tensor, inp: Inputs) -> Optional[bool]:\n    \"\"\"\n    Detects the LSE format if we're in a varlen case.\n    Returns `None` if the format is not relevant (eg not varlen)\n    Raises an exception if the `lse` has the wrong shape\n    \"\"\"\n    shape_mismatch_err = (\n        \"Input tensors have incompatible shapes.\\n\"\n        f\"  lse.shape    : {lse.shape}\\n\"\n        f\"  query.shape  : {inp.query.shape}\\n\"\n        f\"  attn_bias    : {type(inp.attn_bias)}\"\n    )\n    # 1. Check ndim & head dimensions\n    # In any case, LSE should be [*, *GH]\n    if lse.ndim != (inp.query.ndim - 1) or lse.shape[1:-1] != inp.query.shape[2:-1]:\n        raise ValueError(shape_mismatch_err)\n    lse_bm = [lse.shape[0], lse.shape[-1]]\n    lse_packed_shape = [inp.query.shape[0], inp.query.shape[1]]\n    lse_packed = lse_bm[0] == lse_packed_shape[0] and lse_bm >= lse_packed_shape\n    # 2. Check correctness for varlen biases with query.shape = [1, M, *GH, K]\n    # Either [1, *GH, M] (packed)\n    # Or     [num_seq, *GH, Mq] .. with `Mq >= max_q` (padded)\n    if isinstance(inp.attn_bias, VARLEN_BIASES):\n        si = inp.attn_bias.q_seqinfo\n        lse_padded_shape = [si.seqstart.shape[0] - 1, si.max_seqlen]\n        lse_padded = lse_bm[0] == lse_padded_shape[0] and lse_bm >= lse_padded_shape\n        if lse_packed and lse_padded:\n            return None\n        elif lse_packed:\n            return True\n        elif lse_padded:\n            return False\n        raise ValueError(shape_mismatch_err)\n    # 3. For non-varlen, shape must be [B, *GH] with query.shape=[B, M, *GH, K]\n    if not lse_packed:\n        raise ValueError(shape_mismatch_err)\n    return None\n\n\ndef _memory_efficient_attention_backward(\n    ctx: Context,\n    inp: Inputs,\n    grad: torch.Tensor,\n    op: Optional[Type[AttentionBwOpBase]],\n    *,\n    _skip_op_checks: bool = False,\n) -> Gradients:\n    \"\"\"Warning: grad/ctx.out is potentially in BMK format\"\"\"\n    inp.validate_inputs()\n    if grad.ndim != inp.query.ndim or grad.ndim != ctx.out.ndim:\n        raise ValueError(\n            \"All tensors should be either in BMK (ndim=3) or BMHK (ndim=4) format. \\n\"\n            f\"grad.shape : {grad.shape} \\n\"\n            f\"out.shape  : {ctx.out.shape} \\n\"\n            f\"query.shape: {inp.query.shape}\"\n        )\n    shape_dq, shape_dk, shape_dv = tuple(\n        x.shape for x in (inp.query, inp.key, inp.value)\n    )\n    inp.normalize_bmhk()\n    varlen_lse_packed = _detect_lse_packed_or_raise(ctx.lse, inp)\n    grad = bmk2bmhk(grad, 1)\n    ctx.out = bmk2bmhk(ctx.out, 1)\n\n    if op is None:\n        op = _dispatch_bw(inp, varlen_lse_packed=varlen_lse_packed)\n    elif not _skip_op_checks:\n        _ensure_op_supports_or_raise(\n            ValueError, \"memory_efficient_attention_backward\", op, inp\n        )\n        if varlen_lse_packed is not None and varlen_lse_packed != op.VARLEN_LSE_PACKED:\n            raise ValueError(\n                f\"Wrong LSE format for {op.NAME} in variable seqlen case. \"\n                f\"Double-check that the BW operator {op.NAME} is compatible \"\n                f\"with the operator used in the FW pass.\"\n            )\n\n    grads = op.apply(ctx, inp, grad)\n    grads.dq = grads.dq.reshape(shape_dq)\n    grads.dk = grads.dk.reshape(shape_dk)\n    grads.dv = grads.dv.reshape(shape_dv)\n    return grads\n\n\ndef memory_efficient_attention_partial(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[Union[AttentionOp, Type[AttentionFwOpBase]]] = None,\n    output_dtype: Optional[torch.dtype] = None,\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    \"\"\"\n    Returns a tuple (output, lse), where `output` is the attention in the style of\n    memory_efficient_attention, and  `lse` is extra data, a log-sum-exp.\n    The outputs of calls to this with the same query and separate keys and values\n    can be merged with merge_attentions to obtain the attention of the queries\n    against the disjoint union of the keys and values.\n\n    This function doesn't have a backward pass.\n    \"\"\"\n    if p != 0.0:\n        raise NotImplementedError(\"dropout is not supported.\")\n    fwop: Optional[Type[AttentionFwOpBase]] = op[0] if isinstance(op, tuple) else op\n    inp = Inputs(\n        query=query,\n        key=key,\n        value=value,\n        p=p,\n        attn_bias=attn_bias,\n        scale=scale,\n        output_dtype=output_dtype,\n        is_partial=True,\n    )\n\n    out, ctx = _memory_efficient_attention_forward_requires_grad(\n        inp,\n        op=fwop,\n    )\n    return out, ctx.lse\n\n\ndef merge_attentions(\n    attn_split: Union[torch.Tensor, Sequence[torch.Tensor]],\n    lse_split: Union[torch.Tensor, Sequence[torch.Tensor]],\n    write_lse: bool = True,\n    output_dtype: Optional[torch.dtype] = None,\n) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:\n    \"\"\"\n    Combine attention output computed on different parts of K/V for the same\n    query to get attention on the whole K/V. See https://arxiv.org/abs/2402.05099\n    The result is equal to\n        Out_full = (Out1 * exp(LSE1) + Out2 * exp(LSE2) + ...) / (exp(LSE1) + exp(LSE2) + ...)\n        LSE_full = log(exp(LSE1) + exp(LSE2) + ...)\n\n    Args:\n        attn_split: attention outputs for chunks,\n            either as a list of tensors of shapes [B, M, G, H, Kq] or [B, M, H, Kq]\n            or as a single tensor of shape [num_chunks, B, M, G, H, Kq]\n            or [num_chunks, B, M, H, Kq]\n        lse_split: LSE for chunks,\n            either as a list of tensors of shapes [B, G, H, M] or [B, H, M]\n            or as a single tensor of shape [num_chunks, B, G, H, M] or [num_chunks, B, H, M]\n        write_lse: whether to output LSE\n        output_dtype: dtype of attn_out\n\n    Returns:\n        attn_out: [B, M, G, H, Kq] or [B, M, H, Kq]\n        lse_out: [B, G, H, M] or [B, H, M] if write_lse\n                 or None otherwise\n    \"\"\"\n\n    attn_is_concat = isinstance(attn_split, torch.Tensor)\n    lse_is_concat = isinstance(lse_split, torch.Tensor)\n\n    attn_requires_grad = (\n        attn_split.requires_grad  # type: ignore\n        if attn_is_concat\n        else any(x.requires_grad for x in attn_split)\n    )\n    lse_requires_grad = (\n        lse_split.requires_grad  # type: ignore\n        if lse_is_concat\n        else any(x.requires_grad for x in lse_split)\n    )\n    requires_grad = torch.is_grad_enabled() and (\n        attn_requires_grad or lse_requires_grad\n    )\n    if requires_grad and not write_lse:\n        raise ValueError(\"write_lse should be true if inputs require gradients.\")\n\n    concat_path = attn_is_concat and lse_is_concat and not requires_grad\n    if concat_path:\n        attn_split = cast(torch.Tensor, attn_split)\n        lse_split = cast(torch.Tensor, lse_split)\n        if attn_split.ndim != lse_split.ndim + 1:\n            raise ValueError(\n                f\"Incompatible input shapes: {attn_split.shape=}, {lse_split.shape=}\"\n            )\n\n        is_bmhk = attn_split.ndim == 5\n        if is_bmhk:\n            attn_split = attn_split.unsqueeze(3)\n            lse_split = lse_split.unsqueeze(2)\n\n        num_chunks, B, M, G, H, Kq = attn_split.shape\n        num_chunks1, B1, G1, H1, M1 = lse_split.shape\n        if B != B1 or G != G1 or H != H1 or num_chunks != num_chunks1 or M != M:\n            raise ValueError(\n                f\"Incompatible input shapes: {attn_split.shape=} {lse_split.shape=} \"\n                f\"{B}/{B1}, {G}/{G1}, {H}/{H1}, {num_chunks}/{num_chunks1}, {M}/{M}\"\n            )\n\n        attn_split = attn_split.permute(1, 3, 4, 0, 2, 5)\n        lse_split = lse_split.permute(1, 2, 3, 0, 4)\n\n        device = attn_split.device\n        attn_dtype = attn_split.dtype\n        lse_dtype = lse_split.dtype\n    else:\n        if attn_is_concat:\n            attn_split = attn_split.unbind(0)  # type: ignore\n        if lse_is_concat:\n            lse_split = lse_split.unbind(0)  # type: ignore\n        num_chunks = len(attn_split)\n        if len(lse_split) != num_chunks:\n            raise ValueError(\n                f\"Incompatible number of LSE and attention chunks: {len(attn_split)=}, {len(lse_split)=}\"\n            )\n\n        attn_unsqueezed = []\n        lse_unsqueezed = []\n        is_bmhk = False\n        for i in range(num_chunks):\n            if attn_split[i].ndim != lse_split[i].ndim + 1:\n                raise ValueError(\n                    f\"Incompatible input shapes for chunk {i}: {attn_split[i].shape=}, {lse_split[i].shape=}\"\n                )\n\n            is_bmhk = attn_split[i].ndim == 4\n            if is_bmhk:\n                attn_unsqueezed.append(attn_split[i].unsqueeze(2))\n                lse_unsqueezed.append(lse_split[i].unsqueeze(1))\n            else:\n                attn_unsqueezed.append(attn_split[i])\n                lse_unsqueezed.append(lse_split[i])\n        attn_split, lse_split = attn_unsqueezed, lse_unsqueezed\n\n        B, M, G, H, Kq = attn_split[0].shape\n        B1, G1, H1, M1 = lse_split[0].shape\n        if B != B1 or G != G1 or H != H1 or M != M:\n            raise ValueError(\n                f\"Incompatible input shapes: {attn_split[0].shape=}, {lse_split[0].shape=} \"\n                f\"{B}/{B1}, {G}/{G1}, {H}/{H1}, {M}/{M}\"\n            )\n\n        for i in range(num_chunks):\n            if attn_split[i].shape != (B, M, G, H, Kq):\n                raise ValueError(\n                    f\"Incompatible input shapes for attention chunk {i}: \"\n                    f\"{attn_split[i].shape=}, {(B, M, G, H, Kq)=}\"\n                )\n            if lse_split[i].shape != (B, G, H, M):\n                raise ValueError(\n                    f\"Incompatible input shapes for LSE chunk {i}: \"\n                    f\"{lse_split[i].shape=}, {(B, G, H, M)=}\"\n                )\n\n            attn_split[i] = attn_split[i].permute(0, 2, 3, 1, 4)  # to (B, G, H, M, Kq)\n\n        device = attn_split[0].device\n        attn_dtype = attn_split[0].dtype\n        lse_dtype = lse_split[0].dtype\n\n    if concat_path:\n        attn_out = torch.empty(\n            B,\n            M,\n            G,\n            H,\n            Kq,\n            device=device,\n            dtype=output_dtype or attn_dtype,\n        )\n        if write_lse:\n            lse_out = torch.empty(\n                B,\n                G,\n                H,\n                M,\n                device=device,\n                dtype=lse_dtype,\n            )\n        else:\n            lse_out = None\n        triton_splitk.merge_attentions(attn_out, lse_out, attn_split, lse_split)  # type: ignore\n    else:\n        outs = triton_splitk.merge_attentions_varargs(\n            attn_split, lse_split, write_lse, output_dtype, B, M, G, H, Kq\n        )  # type: ignore\n        attn_out = outs[0]\n        lse_out = outs[1] if write_lse else None\n\n    if is_bmhk:\n        attn_out = attn_out[:, :, 0]\n        if lse_out is not None:\n            lse_out = lse_out[:, 0]\n\n    return attn_out, lse_out\n\n\nALL_FW_OPS: List[Type[AttentionFwOpBase]] = [\n    cutlass.FwOp if torch.version.cuda else ck.FwOp,\n    cutlass_blackwell.FwOp,\n    flash.FwOp,\n    flash3.FwOp,\n    triton_splitk.FwOp,\n]\n\nALL_BW_OPS: List[Type[AttentionBwOpBase]] = [\n    cutlass.BwOp if torch.version.cuda else ck.BwOp,\n    cutlass_blackwell.BwOp,\n    flash.BwOp,\n    flash3.BwOp,\n]\n\n__all__ = [\n    \"AttentionBias\",\n    \"AttentionOp\",\n    \"AttentionOpBase\",\n    \"LowerTriangularMask\",\n    \"MemoryEfficientAttentionCutlassFwdFlashBwOp\",\n    \"MemoryEfficientAttentionCutlassOp\",\n    \"MemoryEfficientAttentionFlashAttentionOp\",\n    \"memory_efficient_attention\",\n    \"MemoryEfficientAttentionCkOp\",\n    \"MemoryEfficientAttentionSplitKCkOp\",\n    \"ALL_FW_OPS\",\n    \"ALL_BW_OPS\",\n    \"attn_bias\",\n    \"_get_use_fa3\",\n    \"_set_use_fa3\",\n    \"BlockDiagonalMask\",\n]\n"
  },
  {
    "path": "xformers/ops/fmha/_triton/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "xformers/ops/fmha/_triton/splitk_kernels.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport functools\nimport sys\nfrom typing import Callable, Dict, Tuple, Union\n\nimport torch\nimport triton\nimport triton.language as tl\n\nfrom xformers.triton.vararg_kernel import unroll_varargs, VAR_ARGS_ARRAY\n\nAUTOTUNER_KEY = [\n    \"Z\",\n    \"H\",\n    \"G\",\n    \"N_CTX_Q\",\n    \"N_CTX_K\",\n    \"BLOCK_DMODEL\",\n    \"PACKED_PER_VAL\",\n    \"N_GROUPS\",\n    \"BLOCK_N_PER_SPLIT\",\n    \"PAGE_SIZE\",\n]\n\n\n@triton.jit\ndef _fwd_kernel_splitK(\n    Q,\n    K,\n    V,\n    sm_scale,\n    Out_splitK,  # [B, H, split_k, Mq, K]\n    LSE_splitk,  # [B, H, split_k, Mq]\n    block_tables,\n    Seq_len,\n    Seq_starts_k,\n    Seq_starts_q,\n    Seq_starts_q_multiplier,\n    additive_bias,\n    K_fp8_scale_shift,\n    V_fp8_scale_shift,\n    stride_qz,\n    stride_qm,\n    stride_qg,\n    stride_qh,\n    stride_qk,\n    stride_kz,\n    stride_kn,\n    stride_kg,\n    stride_kh,\n    stride_kk,\n    stride_vz,\n    stride_vn,\n    stride_vg,\n    stride_vh,\n    stride_vk,\n    stride_osk_z,\n    stride_osk_g,\n    stride_osk_h,\n    stride_osk_s,\n    stride_osk_m,\n    stride_osk_k,\n    stride_lsek_z,\n    stride_lsek_g,\n    stride_lsek_h,\n    stride_lsek_s,\n    stride_lsek_m,\n    stride_blocktablesz,\n    stride_blocktablesl,\n    stride_bias_b,\n    stride_bias_g,\n    stride_bias_h,\n    stride_bias_qm,\n    stride_bias_km,\n    stride_k_fp8_scale_shift_z: tl.constexpr,\n    stride_k_fp8_scale_shift_n: tl.constexpr,\n    stride_k_fp8_scale_shift_g: tl.constexpr,\n    stride_k_fp8_scale_shift_h: tl.constexpr,\n    stride_v_fp8_scale_shift_z: tl.constexpr,\n    stride_v_fp8_scale_shift_n: tl.constexpr,\n    stride_v_fp8_scale_shift_g: tl.constexpr,\n    stride_v_fp8_scale_shift_h: tl.constexpr,\n    kv_cache_blocks_per_row: tl.constexpr,\n    Z: tl.constexpr,\n    N_CTX_Q: tl.constexpr,  # The number of queries\n    N_CTX_K: tl.constexpr,\n    BLOCK_N_PER_SPLIT: tl.constexpr,\n    H: tl.constexpr,\n    G: tl.constexpr,\n    BLOCK_DMODEL: tl.constexpr,\n    USE_SEQ_LEN: tl.constexpr,\n    PACKED_PER_VAL: tl.constexpr,\n    N_GROUPS: tl.constexpr,\n    # It's important that BOUNDS_CHECKS_N, BLOCK_M, BLOCK_N come at the end of\n    # the argument list, since they are provided by the heuristics/autotune decorator.\n    # Otherwise Triton throws IndexError\n    BOUNDS_CHECKS_N: tl.constexpr,\n    BLOCK_M: tl.constexpr,\n    BLOCK_N: tl.constexpr,\n    IS_SPLITK: tl.constexpr,\n    SPLIT_K_EARLY_EXIT: tl.constexpr,\n    IS_CAUSAL: tl.constexpr,\n    IS_LOCAL: tl.constexpr,\n    NUM_QUERIES_CAUSAL: tl.constexpr,  # The N_CTX_Q queries are from this many sequence positions\n    USE_PAGED_ATTENTION: tl.constexpr,\n    PAGE_SIZE: tl.constexpr,\n    WINDOW_LEFT: tl.constexpr,\n    WINDOW_RIGHT: tl.constexpr,\n    WRITE_LSE: tl.constexpr,\n    HAS_ADDITIVE_BIAS: tl.constexpr,\n    NUM_PROGRAMS_DIM2_CONST: tl.constexpr,\n    IS_HIP: tl.constexpr,\n):\n    \"\"\"This kernel can accept non-quantized or int4-quantized keys/values.\n    PACKED_PER_VAL determines the quantization type:\n        - PACKED_PER_VAL == 1 means no quantization\n        - PACKED_PER_VAL == 8 means 4-bit quantization (8 packed quantized values inside one int32)\n    For the quantized case K/V should be int32 tensors.\n    Quantization can be row-wise (when N_GROUPS = 1) or group-wise with N_GROUPS = 2, 4, or 8.\n    Quantization coefficients are stored at the beginning of the row along the last dimension of K/V\n    So K[B, H, M, :] has a form\n    [   quant_coef0, quant_coef1, ...|\n        group0_quant_value0, group0_quant_value1,... |\n        group1_quant_value0, group1_quant_value1,...]\n    where each quant_coef is an int32 which should be interpreted as 2 packed float16: scale and offset.\n\n    Note: this kernel needs to be processed by xformers.triton.vararg_kernel.unroll_varargs\n    before compilation. That will unroll variables marked with \"VAR_ARGS_ARRAY\" into lists.\n    See how FwOp.apply does it below.\n\n    Set IS_SPLITK=False to indicate the MHA result should be written directly.\n    No metadata will be written.\n    \"\"\"\n    internal_dtype = (\n        tl.float64 if Out_splitK.dtype.element_ty is tl.float64 else tl.float32\n    )\n    tl.static_assert(\n        (PACKED_PER_VAL == 1 and tl.constexpr(K.dtype.element_ty != tl.int32))\n        or (\n            (PACKED_PER_VAL == 4 or PACKED_PER_VAL == 8)\n            and tl.constexpr(K.dtype.element_ty == tl.int32)\n        ),\n        f\"Only int4 and fp8 quantization is supported, K/V should have dtype int32 in \"\n        f\"the quantized case: {PACKED_PER_VAL=} {tl.constexpr(K.dtype)=} {tl.constexpr(K.dtype.element_ty)=}\",\n    )\n    tl.static_assert(\n        (((N_GROUPS == 1 or N_GROUPS == 2) or N_GROUPS == 4) or N_GROUPS == 8),\n        \"Number of quantization groups can be 1 (row-wise quantization), 2, 4, or 8.\",\n    )\n    tl.static_assert(\n        N_GROUPS == 1 or K_fp8_scale_shift is None,\n        f\"Only row-wise fp8 quantization is supported, but got {N_GROUPS=} > 1.\",\n    )\n    FP8_QUANTIZED: tl.constexpr = K_fp8_scale_shift is not None\n    INT4_QUANTIZED: tl.constexpr = PACKED_PER_VAL > 1 and not FP8_QUANTIZED\n    PACKED_D_PER_GROUP: tl.constexpr = BLOCK_DMODEL // PACKED_PER_VAL // N_GROUPS\n    D_PER_GROUP: tl.constexpr = BLOCK_DMODEL // N_GROUPS\n\n    start_m = tl.program_id(0)\n    off_zhg = tl.program_id(1)\n    off_z = (off_zhg // (H * G)).to(tl.int64)\n    off_hg = off_zhg % (H * G)\n    off_h = off_hg // G\n    off_g = off_hg % G\n    splitk_idx = tl.program_id(2)\n\n    if USE_SEQ_LEN:\n        kv_len = tl.load(Seq_len + off_z)\n        if SPLIT_K_EARLY_EXIT and kv_len == 0:\n            return\n    else:\n        kv_len = N_CTX_K\n\n    if Seq_starts_k is None:\n        start_kv_idx = 0\n    else:\n        start_kv_idx = tl.load(Seq_starts_k + off_z)\n        if USE_SEQ_LEN and PAGE_SIZE > 0:\n            # gappy with paged attention stores each \"end\" instead of each \"length\"\n            # because that's what FA3 needs.\n            kv_len -= start_kv_idx\n\n    if Seq_starts_q is None:\n        q_len = N_CTX_Q\n        queries_use_batch_dim = 1\n        off_m = 0\n    else:\n        queries_use_batch_dim = 0\n        off_m = tl.load(Seq_starts_q + off_z) * Seq_starts_q_multiplier\n        q_len = tl.load(Seq_starts_q + off_z + 1) * Seq_starts_q_multiplier - off_m\n        if q_len == 0:\n            return\n\n    k_base = K + off_h * stride_kh + off_g * stride_kg\n    v_base = V + off_h * stride_vh + off_g * stride_vg\n\n    if FP8_QUANTIZED:\n        k_fp8_scale_shift_base = (\n            K_fp8_scale_shift\n            + off_h * stride_k_fp8_scale_shift_h\n            + off_g * stride_k_fp8_scale_shift_g\n        )\n        v_fp8_scale_shift_base = (\n            V_fp8_scale_shift\n            + off_h * stride_v_fp8_scale_shift_h\n            + off_g * stride_v_fp8_scale_shift_g\n        )\n    else:\n        k_fp8_scale_shift_base = None\n        v_fp8_scale_shift_base = None\n\n    # Boundaries of split-k chunk\n    chunk_hi = (splitk_idx + 1) * BLOCK_N_PER_SPLIT\n    chunk_lo = splitk_idx * BLOCK_N_PER_SPLIT\n    ignore_in_first_block = 0\n    # For paged attention case K/V_block_ptr are defined inside the loop\n    # whereas for non-paged case they are defined before the loop.\n    if PAGE_SIZE > 0:\n        # Page contains several blocks\n        BLOCKS_IN_PAGE: tl.constexpr = PAGE_SIZE // BLOCK_N\n        # Align boundaries of split-k chunk to block boundaries\n        # In the last chunk, shift hi to the right, in the other chunks, shift it to the left\n        # TODO: Replace NUM_PROGRAMS_DIM2_CONST with tl.num_programs(2) after\n        # the next Triton upgrade.\n        is_last_chunk = splitk_idx == NUM_PROGRAMS_DIM2_CONST - 1\n        shift = BLOCK_N - 1 if is_last_chunk else 0\n        lo = (tl.maximum(chunk_lo, start_kv_idx) // BLOCK_N) * BLOCK_N\n        ignore_in_first_block = tl.maximum(0, (start_kv_idx - lo))\n        hi = ((chunk_hi + shift) // BLOCK_N) * BLOCK_N\n        hi = tl.minimum(hi, kv_len + start_kv_idx)\n        block_table = block_tables + stride_blocktablesz * off_z\n        # Offset in integer blocks\n        logical_block_idx = lo // BLOCK_N\n    else:\n        lo = chunk_lo\n        hi = tl.minimum(chunk_hi, kv_len)\n        if Seq_starts_k is not None:\n            k_base += start_kv_idx * stride_kn\n            v_base += start_kv_idx * stride_vn\n        else:\n            k_base += off_z * stride_kz\n            v_base += off_z * stride_vz\n        # Additional shift by 1 along the last dimension in the quantized case, since\n        # the first element along that dim contains packed quantization coefficients.\n        K_block_ptr = tl.make_block_ptr(\n            base=k_base + stride_kk * INT4_QUANTIZED * N_GROUPS,\n            shape=(PACKED_D_PER_GROUP, hi),\n            strides=(stride_kk, stride_kn),\n            offsets=(0, lo),\n            block_shape=(PACKED_D_PER_GROUP, BLOCK_N),\n            order=(0, 1),\n        )\n        V_block_ptr = tl.make_block_ptr(\n            base=v_base + stride_vk * INT4_QUANTIZED * N_GROUPS,\n            shape=(hi, PACKED_D_PER_GROUP),\n            strides=(stride_vn, stride_vk),\n            offsets=(lo, 0),\n            block_shape=(BLOCK_N, PACKED_D_PER_GROUP),\n            order=(1, 0),\n        )\n\n        if INT4_QUANTIZED:\n            # Pointers to quantization coefficients. Even those they are 1D,\n            # we use block pointers here so the pointer arithmetic is in int64,\n            # as otherwise the offsets for V_scale_shift_block_ptr may overflow.\n            K_scale_shift_block_ptr = tl.make_block_ptr(\n                base=k_base,\n                shape=(1, hi),\n                strides=(stride_kk, stride_kn),\n                offsets=(0, lo),\n                block_shape=(1, BLOCK_N),\n                order=(0, 1),\n            )\n            V_scale_shift_block_ptr = tl.make_block_ptr(\n                base=v_base,\n                shape=(hi, 1),\n                strides=(stride_vn, stride_vk),\n                offsets=(lo, 0),\n                block_shape=(BLOCK_N, 1),\n                order=(1, 0),\n            )\n        elif FP8_QUANTIZED:\n            if Seq_starts_k is not None:\n                k_fp8_scale_shift_base += start_kv_idx * stride_k_fp8_scale_shift_n\n                v_fp8_scale_shift_base += start_kv_idx * stride_v_fp8_scale_shift_n\n            else:\n                k_fp8_scale_shift_base += off_z * stride_k_fp8_scale_shift_z\n                v_fp8_scale_shift_base += off_z * stride_v_fp8_scale_shift_z\n            K_scale_shift_block_ptr = tl.make_block_ptr(\n                base=k_fp8_scale_shift_base,\n                shape=(1, hi),\n                strides=(1, stride_k_fp8_scale_shift_n),\n                offsets=(0, lo),\n                block_shape=(1, BLOCK_N),\n                order=(0, 1),\n            )\n            V_scale_shift_block_ptr = tl.make_block_ptr(\n                base=v_fp8_scale_shift_base,\n                shape=(hi, 1),\n                strides=(stride_v_fp8_scale_shift_n, 1),\n                offsets=(lo, 0),\n                block_shape=(BLOCK_N, 1),\n                order=(1, 0),\n            )\n        else:\n            K_scale_shift_block_ptr = None\n            V_scale_shift_block_ptr = None\n\n        if HAS_ADDITIVE_BIAS:\n            additive_bias_block_ptr = tl.make_block_ptr(\n                base=additive_bias\n                + off_z * stride_bias_b\n                + off_g * stride_bias_g\n                + off_h * stride_bias_h,\n                shape=(N_CTX_Q, hi),\n                strides=(stride_bias_qm, stride_bias_km),\n                offsets=(start_m * BLOCK_M, lo),\n                block_shape=(BLOCK_M, BLOCK_N),\n                order=(0, 1),\n            )\n\n    if SPLIT_K_EARLY_EXIT and lo >= hi:\n        return\n\n    Q_block_ptr = tl.make_block_ptr(\n        base=Q\n        + off_m * stride_qm\n        + off_h * stride_qh\n        + off_z * stride_qz * queries_use_batch_dim\n        + off_g * stride_qg,\n        shape=(q_len, BLOCK_DMODEL),\n        strides=(stride_qm, stride_qk),\n        offsets=(start_m * BLOCK_M, 0),\n        block_shape=(BLOCK_M, D_PER_GROUP),\n        order=(1, 0),\n    )\n\n    # initialize pointer to m and l\n    m_i = tl.zeros([BLOCK_M], dtype=tl.float32) - float(\"inf\")\n    l_i = tl.zeros([BLOCK_M], dtype=tl.float32)\n\n    # Before compilation, this kernel will be processed by xformers.triton.vararg_kernel.unroll_varargs.\n    # That turns tensors annotated as the one below into lists of tensors of length N_GROUPS.\n    # This is a solution for Triton native lack of support for lists of tensors.\n    acc: \"VAR_ARGS_ARRAY\"  # noqa: F821\n\n    for i in range(len(acc)):  # noqa: F821\n        acc[i] = tl.zeros([BLOCK_M, D_PER_GROUP], dtype=internal_dtype)  # noqa: F821\n    # scale sm_scale by log_2(e) and use\n    # 2^x instead of exp in the loop because CSE and LICM\n    # don't work as expected with `exp` in the loop\n    #\n    # We declare log2e as a constant with a precisely-specified type to guarantee that\n    # triton will use the exact same value in all instances below, rather than sometimes\n    # using float32 and sometimes using float64.  For more discussion see:\n    # https://github.com/triton-lang/triton/issues/5466\n    log2e = tl.full((), 1.44269504, tl.float32)\n    qk_scale = sm_scale * log2e\n    # load q: it will stay in SRAM throughout\n    q: \"VAR_ARGS_ARRAY\"  # noqa: F821\n    for i in range(len(acc)):  # noqa: F821\n        q[i] = tl.load(  # noqa: F821\n            tl.advance(Q_block_ptr, (0, i * D_PER_GROUP)), boundary_check=(0,)\n        )\n\n    if IS_CAUSAL or IS_LOCAL:\n        # Why does the masking conditon below work as a causal mask?\n        # Assuming num_queries <= BLOCK_M:\n        # kv_pos = kv_start + range(0, BLOCK_N)\n        # q_offset = start_m * BLOCK_M + range(0, BLOCK_M)\n        # q_pos = kv_start + kv_len - num_queries + q_offset % num_queries\n        # mask = q_pos - kv_pos >= 0\n        # So the final masking condition is:\n        #   range(0, BLOCK_M) % num_queries - range(0, BLOCK_N) >= num_queries - kv_len\n\n        q_offset = start_m * BLOCK_M + tl.arange(0, BLOCK_M)\n        diag_idx = (q_offset[:, None] % NUM_QUERIES_CAUSAL) - tl.arange(0, BLOCK_N)[\n            None, :\n        ]\n        diag_idx_shifted = tl.constexpr(diag_idx - NUM_QUERIES_CAUSAL + kv_len)\n\n    # loop over k, v and update accumulator\n    for start_n in range(lo, hi, BLOCK_N):\n        if PAGE_SIZE > 0:\n            # Offset in integer blocks from the beginning of the page\n            block_offset_in_page = logical_block_idx % BLOCKS_IN_PAGE\n            # Offset in integer pages\n            logical_page_idx = logical_block_idx // BLOCKS_IN_PAGE\n            physical_page_idx = tl.load(\n                block_table + stride_blocktablesl * logical_page_idx\n            ).to(tl.int32)\n            offset = physical_page_idx * PAGE_SIZE + block_offset_in_page * BLOCK_N\n\n            current_block_size = min(hi - start_n, BLOCK_N)\n            K_block_ptr = tl.make_block_ptr(\n                base=k_base + stride_kk * INT4_QUANTIZED * N_GROUPS,\n                shape=(PACKED_D_PER_GROUP, offset + current_block_size),\n                strides=(stride_kk, stride_kn),\n                offsets=(0, offset),\n                block_shape=(PACKED_D_PER_GROUP, BLOCK_N),\n                order=(0, 1),\n            )\n            V_block_ptr = tl.make_block_ptr(\n                base=v_base + stride_vk * INT4_QUANTIZED * N_GROUPS,\n                shape=(offset + current_block_size, PACKED_D_PER_GROUP),\n                strides=(stride_vn, stride_vk),\n                offsets=(offset, 0),\n                block_shape=(BLOCK_N, PACKED_D_PER_GROUP),\n                order=(1, 0),\n            )\n            if INT4_QUANTIZED:\n                # Pointers to quantization coefficients. Even those they are 1D,\n                # we use block pointers here so the pointer arithmetic is in int64,\n                # as otherwise the offsets for V_scale_shift_block_ptr may overflow.\n                K_scale_shift_block_ptr = tl.make_block_ptr(\n                    base=k_base,\n                    shape=(1, offset + current_block_size),\n                    strides=(stride_kk, stride_kn),\n                    offsets=(0, offset),\n                    block_shape=(1, BLOCK_N),\n                    order=(0, 1),\n                )\n                V_scale_shift_block_ptr = tl.make_block_ptr(\n                    base=v_base,\n                    shape=(offset + current_block_size, 1),\n                    strides=(stride_vn, stride_vk),\n                    offsets=(offset, 0),\n                    block_shape=(BLOCK_N, 1),\n                    order=(1, 0),\n                )\n            elif FP8_QUANTIZED:\n                K_scale_shift_block_ptr = tl.make_block_ptr(\n                    base=k_fp8_scale_shift_base,\n                    shape=(1, offset + current_block_size),\n                    strides=(1, stride_k_fp8_scale_shift_n),\n                    offsets=(0, offset),\n                    block_shape=(1, BLOCK_N),\n                    order=(0, 1),\n                )\n                V_scale_shift_block_ptr = tl.make_block_ptr(\n                    base=v_fp8_scale_shift_base,\n                    shape=(offset + current_block_size, 1),\n                    strides=(stride_v_fp8_scale_shift_n, 1),\n                    offsets=(offset, 0),\n                    block_shape=(BLOCK_N, 1),\n                    order=(1, 0),\n                )\n            else:\n                K_scale_shift_block_ptr = None\n                V_scale_shift_block_ptr = None\n            logical_block_idx += 1\n\n        k: \"VAR_ARGS_ARRAY\"  # noqa: F821\n        v: \"VAR_ARGS_ARRAY\"  # noqa: F821\n        for i in range(len(acc)):  # noqa: F821\n            k[i], v[i] = load_dequantize_k_v_group(  # noqa: F821\n                K_block_ptr,\n                V_block_ptr,\n                K_scale_shift_block_ptr,\n                V_scale_shift_block_ptr,\n                BOUNDS_CHECKS_N,\n                PACKED_PER_VAL,\n                PACKED_D_PER_GROUP,\n                FP8_QUANTIZED,\n                Q.dtype.element_ty,\n                i,\n                IS_HIP,\n            )\n\n        # -- compute qk ---\n        qk = tl.zeros([BLOCK_M, BLOCK_N], dtype=tl.float32)\n        for i in range(len(acc)):  # noqa: F821\n            qk += tl.dot(q[i], k[i])  # noqa: F821\n        qk *= qk_scale\n\n        if start_n == lo and ignore_in_first_block > 0:\n            qk = tl.where(\n                tl.arange(0, BLOCK_N) < ignore_in_first_block, float(\"-inf\"), qk\n            )\n\n        if HAS_ADDITIVE_BIAS:\n            loaded_bias = tl.load(\n                additive_bias_block_ptr,\n                boundary_check=(0, 1) if BOUNDS_CHECKS_N else (0,),\n            )\n            qk += loaded_bias.to(tl.float32) * log2e\n            additive_bias_block_ptr = tl.advance(additive_bias_block_ptr, (0, BLOCK_N))\n\n        # TODO: This is slow, and only needed at the last iteration.\n        # Maybe we can unroll the last iteration instead?\n        if BOUNDS_CHECKS_N:\n            qk = tl.where(tl.arange(0, BLOCK_N) < hi - start_n, qk, float(\"-inf\"))\n        if IS_CAUSAL:\n            # -- apply the causal mask --\n            qk = tl.where(diag_idx_shifted >= start_n - start_kv_idx, qk, float(\"-inf\"))\n        if IS_LOCAL:\n            # -- apply the local window size mask --\n            qk = tl.where(\n                diag_idx_shifted < start_n - start_kv_idx + WINDOW_LEFT + 1,\n                qk,\n                float(\"-inf\"),\n            )\n            if not IS_CAUSAL and WINDOW_RIGHT >= 0:\n                qk = tl.where(\n                    diag_idx_shifted >= start_n - start_kv_idx - WINDOW_RIGHT,\n                    qk,\n                    float(\"-inf\"),\n                )\n\n        # -- compute scaling constant ---\n        m_i_new = tl.maximum(m_i, tl.max(qk, 1))\n        alpha = tl.math.exp2(m_i - m_i_new)\n        p = tl.math.exp2(qk - m_i_new[:, None])\n        if HAS_ADDITIVE_BIAS or (IS_CAUSAL or IS_LOCAL):\n            # NOTE: It's possible that an entire block is masked out.\n            # if this is the case, `m_i_new=nan` and everything becomes nan\n            alpha = tl.where(m_i_new == float(\"-inf\"), 0, alpha)\n            p = tl.where(m_i_new[:, None] == float(\"-inf\"), 0, p)\n\n        # -- update m_i and l_i --\n        l_i = l_i * alpha + tl.sum(p, 1)\n        m_i = m_i_new\n        p = p.to(Q.dtype.element_ty)\n\n        # -- scale and update acc --\n        for i in range(len(acc)):  # noqa: F821\n            acc[i] *= alpha[:, None]  # noqa: F821\n            acc[i] += tl.dot(p, v[i])  # noqa: F821\n\n        if not PAGE_SIZE:\n            # update pointers\n            K_block_ptr = tl.advance(K_block_ptr, (0, BLOCK_N))\n            V_block_ptr = tl.advance(V_block_ptr, (BLOCK_N, 0))\n            if PACKED_PER_VAL > 1:\n                K_scale_shift_block_ptr = tl.advance(\n                    K_scale_shift_block_ptr, (0, BLOCK_N)\n                )\n                V_scale_shift_block_ptr = tl.advance(\n                    V_scale_shift_block_ptr, (BLOCK_N, 0)\n                )\n\n    # write back O\n    O_block_ptr = tl.make_block_ptr(\n        base=Out_splitK\n        + off_z.to(tl.int64) * stride_osk_z * queries_use_batch_dim\n        + off_m * stride_osk_m\n        + off_g * stride_osk_g\n        + off_h * stride_osk_h\n        + splitk_idx * stride_osk_s,\n        shape=(q_len, D_PER_GROUP),\n        strides=(stride_osk_m, 1),\n        offsets=(start_m * BLOCK_M, 0),\n        block_shape=(BLOCK_M, D_PER_GROUP),\n        order=(1, 0),\n    )\n    for i in range(len(acc)):  # noqa: F821\n        # If for the current batch element there are no tokens in the current split-k chunk (because\n        # seqlen is too short), l_i will be 0, so we need to make sure attention is filled with zeros and not NaNs.\n        attn_out = tl.where(l_i[:, None] == 0, 0.0, acc[i] / l_i[:, None])  # noqa: F821\n        tl.store(\n            tl.advance(O_block_ptr, (0, i * D_PER_GROUP)),\n            attn_out.to(Out_splitK.dtype.element_ty),  # noqa: F821\n            boundary_check=(0,),\n        )\n    if WRITE_LSE:\n        LSE_splitk_ptr = (\n            LSE_splitk\n            + off_z * stride_lsek_z * queries_use_batch_dim\n            + off_m * stride_lsek_m\n            + off_g * stride_lsek_g\n            + off_h * stride_lsek_h\n            + splitk_idx * stride_lsek_s\n            + (start_m * BLOCK_M + tl.arange(0, BLOCK_M)) * stride_lsek_m\n        )\n        mask = start_m * BLOCK_M + tl.arange(0, BLOCK_M) < q_len\n        # Can be float64 to improve numerics\n        lse_dtype = LSE_splitk.dtype.element_ty\n        tl.store(\n            LSE_splitk_ptr,\n            (tl.math.log2(l_i.to(lse_dtype)) + m_i.to(lse_dtype)) / log2e,\n            mask=mask,\n        )\n\n\ndef gen_config(\n    block_m: int,\n    block_n: int,\n    stages: int,\n    warps: int,\n) -> triton.Config:\n    \"\"\"A more compact way to define a triton.Config, so it fits on one line\"\"\"\n\n    return triton.Config(\n        {\n            \"BLOCK_M\": block_m,\n            \"BLOCK_N\": block_n,\n        },\n        num_stages=stages,\n        num_warps=warps,\n    )\n\n\ndef _get_splitk_kernel(num_groups):\n    \"\"\"\n    Kernel _fwd_kernel_splitK needs to be post-processed by unroll_varargs\n    to specialize it for a given number of quantization groups N_GROUPS\n    before we can apply triton.heuristics and triton.autotune, so we\n    don't do them as decorators.\n    \"\"\"\n\n    _fwd_kernel_splitK_unrolled = unroll_varargs(_fwd_kernel_splitK, N=num_groups)\n    kernel = triton.heuristics(\n        {\n            \"BOUNDS_CHECKS_N\": lambda args: bool(\n                (args[\"BLOCK_N_PER_SPLIT\"] % args[\"BLOCK_N\"])\n                or (\n                    args[\"BLOCK_N_PER_SPLIT\"] > 0\n                    and args[\"N_CTX_K\"] % args[\"BLOCK_N_PER_SPLIT\"]\n                )\n                or args[\"USE_SEQ_LEN\"]\n            )\n        }\n    )(_fwd_kernel_splitK_unrolled)\n    return kernel\n\n\ndef early_config_prune(configs, named_args, **kwargs):\n    use_paged_attention = kwargs[\"USE_PAGED_ATTENTION\"]\n    page_size = kwargs[\"PAGE_SIZE\"]\n    if use_paged_attention:\n        return list(\n            filter(lambda config: page_size % config.kwargs[\"BLOCK_N\"] == 0, configs)\n        )\n    else:\n        return configs\n\n\n@functools.lru_cache(None)\ndef autotune_kernel(kernel: Callable):\n    BLOCK_M_VALUES = [16, 32, 64, 128]\n    BLOCK_N_VALUES = [16, 32, 64, 128]\n    STAGES_VALUES = [1, 2] if torch.version.hip else [1, 2, 3]\n    WARPS_VALUES = [1, 2, 4, 8]\n\n    TRITON_CONFIGS = [\n        gen_config(block_m, block_n, stages, warps)\n        for block_m in BLOCK_M_VALUES\n        for block_n in BLOCK_N_VALUES\n        for stages in STAGES_VALUES\n        for warps in WARPS_VALUES\n        if block_n >= block_m\n    ]\n\n    kernel = triton.autotune(\n        configs=TRITON_CONFIGS,\n        key=AUTOTUNER_KEY,\n        use_cuda_graph=True,\n        prune_configs_by={\n            \"early_config_prune\": early_config_prune,\n        },\n    )(kernel)\n    return kernel\n\n\n# This object contains forward kernels wrapped into autotuner for different number\n# of quantization groups.\n_fwd_kernel_splitK_autotune: Dict[int, triton.runtime.Autotuner] = {}\n# The loop below:\n# - transforms the jitted kernel with unroll_varargs producing a new kernel of each value of num_groups\n# - wraps the kernel into triton.heuristics\n# - wraps kernel into Triton autotuner. Autotuning itself happens the first time the kernel is called\nif sys.version_info >= (3, 9):\n    # unroll_varargs requires Python 3.9+\n    for num_groups in [1, 2, 4, 8]:\n        _fwd_kernel_splitK_autotune[num_groups] = autotune_kernel(\n            _get_splitk_kernel(num_groups)\n        )\n\n    def get_autotuner_cache(\n        num_groups: int,\n    ) -> Dict[Tuple[Union[int, str]], triton.Config]:\n        \"\"\"Returns a triton.runtime.autotuner.AutoTuner.cache object, which\n        represents mappings from kernel autotune keys (tuples describing kernel inputs)\n        to triton.Config\n        \"\"\"\n        return _fwd_kernel_splitK_autotune[num_groups].cache\n\n    def set_autotuner_cache(\n        cache: Dict[Tuple[Union[int, str]], triton.Config], num_groups: int\n    ) -> None:\n        _fwd_kernel_splitK_autotune[num_groups].cache = cache\n\n\n@triton.jit\ndef load_dequantize_k_v_group(\n    K_block_ptr,\n    V_block_ptr,\n    K_scale_shift_block_ptr,\n    V_scale_shift_block_ptr,\n    BOUNDS_CHECKS_N: tl.constexpr,\n    PACKED_PER_VAL: tl.constexpr,\n    PACKED_D_PER_GROUP: tl.constexpr,\n    FP8_QUANTIZED: tl.constexpr,\n    dtype: tl.constexpr,\n    group_id: tl.constexpr,\n    IS_HIP: tl.constexpr,\n):\n    \"\"\"Load K/V for a given block. In case of int4/fp8-quantized K/V, dequantize them after loading.\n    If quantization is group-wise, use group_id to advance the pointers to the current group.\n    \"\"\"\n    # Advance to the current quantization group\n    K_block_ptr = tl.advance(K_block_ptr, (PACKED_D_PER_GROUP * group_id, 0))\n    V_block_ptr = tl.advance(V_block_ptr, (0, PACKED_D_PER_GROUP * group_id))\n\n    # -- load k, v --\n    k = tl.load(K_block_ptr, boundary_check=(1,) if BOUNDS_CHECKS_N else ())\n    v = tl.load(V_block_ptr, boundary_check=(0,) if BOUNDS_CHECKS_N else ())\n\n    # If K/V are quantized, load quantization coefficients and dequantize.\n    if FP8_QUANTIZED:\n        v_scale_shift = tl.load(\n            V_scale_shift_block_ptr, boundary_check=(0,) if BOUNDS_CHECKS_N else ()\n        )\n        if IS_HIP:\n            # MI300 doesn't have builtin casting instructions for fp8 -> bf16,\n            # so casting to f32 is actually more performant on this workload.\n            v_scale, v_shift = cast_uint32_to_float(v_scale_shift)\n        else:\n            v_scale, v_shift = cast_uint32_to_half2(v_scale_shift)\n        v = dequantize(v, v_scale, v_shift, PACKED_PER_VAL, IS_HIP).to(dtype)\n\n        k_scale_shift = tl.load(\n            K_scale_shift_block_ptr, boundary_check=(1,) if BOUNDS_CHECKS_N else ()\n        )\n        if IS_HIP:\n            k_scale, k_shift = cast_uint32_to_float(k_scale_shift)\n            k = dequantize_k_hip(k, k_scale, k_shift, PACKED_PER_VAL).to(dtype)\n        else:\n            k_scale, k_shift = cast_uint32_to_half2(k_scale_shift)\n            k_t = dequantize(\n                tl.trans(k),\n                tl.trans(k_scale),\n                tl.trans(k_shift),\n                PACKED_PER_VAL,\n                IS_HIP,\n            ).to(dtype)\n            k = tl.trans(k_t)\n    elif PACKED_PER_VAL > 1:\n        # Int4 quantization.\n        K_scale_shift_block_ptr = tl.advance(K_scale_shift_block_ptr, (group_id, 0))\n        V_scale_shift_block_ptr = tl.advance(V_scale_shift_block_ptr, (0, group_id))\n\n        k_scale_shift = tl.load(\n            K_scale_shift_block_ptr, boundary_check=(1,) if BOUNDS_CHECKS_N else ()\n        )\n        v_scale_shift = tl.load(\n            V_scale_shift_block_ptr, boundary_check=(0,) if BOUNDS_CHECKS_N else ()\n        )\n        if IS_HIP:\n            k_scale, k_shift = cast_uint32_to_float(k_scale_shift)\n            v_scale, v_shift = cast_uint32_to_float(v_scale_shift)\n            v = dequantize(v, v_scale, v_shift, PACKED_PER_VAL, IS_HIP).to(dtype)\n            k = dequantize_k_hip(k, k_scale, k_shift, PACKED_PER_VAL).to(dtype)\n        else:\n            k_scale, k_shift = cast_uint32_to_half2(k_scale_shift)\n            v_scale, v_shift = cast_uint32_to_half2(v_scale_shift)\n            v = dequantize(v, v_scale, v_shift, PACKED_PER_VAL, IS_HIP).to(dtype)\n            k_t = dequantize(\n                tl.trans(k),\n                tl.trans(k_scale),\n                tl.trans(k_shift),\n                PACKED_PER_VAL,\n                IS_HIP,\n            ).to(dtype)\n            k = tl.trans(k_t)\n    return k, v\n\n\n@triton.jit\ndef cast_uint32_to_half2(scale_shift):\n    \"\"\"Extract two float16 packed into one int32\"\"\"\n    scale = scale_shift & 0xFFFF\n    shift = scale_shift >> 16\n    scale = scale.to(tl.uint16).to(tl.float16, bitcast=True)\n    shift = shift.to(tl.uint16).to(tl.float16, bitcast=True)\n    return scale, shift\n\n\n@triton.jit\ndef cast_uint32_to_float(scale_shift):\n    \"\"\"Extract two float16 packed into one int32 as float32\"\"\"\n    scale = scale_shift & 0xFFFF\n    shift = scale_shift >> 16\n    scale = scale.to(tl.uint16).to(tl.float16, bitcast=True).to(tl.float32)\n    shift = shift.to(tl.uint16).to(tl.float16, bitcast=True).to(tl.float32)\n    return scale, shift\n\n\n@triton.jit\ndef dequantize_k_hip(\n    x_,\n    scale,\n    shift,\n    PACKED_PER_VAL: tl.constexpr,\n):\n    \"\"\"PACKED_PER_VAL is the number of values packed into each element x_.\n    For example, for int4 quantization and x_ of type int32, PACKED_PER_VAL is 8.\n    \"\"\"\n    # x_ : (BLOCK_N, D // PACKED_PER_VAL)\n    # scale: (BLOCK_N, 1)\n    # offsets: (PACKED_PER_VAL,)\n    BLOCK_N: tl.constexpr = x_.shape[1]\n    BLOCK_DMODEL_PACKED: tl.constexpr = x_.shape[0]\n    offsets = tl.arange(0, PACKED_PER_VAL) * (32 // PACKED_PER_VAL)\n    quant_offset = (\n        x_[:, None, :, :] >> offsets[:, None]\n    )  # (BLOCK_N, D // PACKED_PER_VAL, PACKED_PER_VAL)\n\n    quant_offset = tl.reshape(\n        quant_offset, (BLOCK_DMODEL_PACKED * PACKED_PER_VAL, BLOCK_N)\n    )\n\n    if PACKED_PER_VAL == 4:\n        # FP8 quantization.\n        fp8_type = tl.float8e4b8 if torch.version.hip is not None else tl.float8e4nv\n        dequant = (\n            quant_offset.to(tl.uint8).to(fp8_type, bitcast=True).to(scale.dtype) * scale\n            + shift\n        )\n    else:\n        # Int4 quantization.\n        # Trick - instead of converting int4 to float16 we view it as float16\n        # and then multiply by 32768 * 512 == 2**24\n        quant_offset = (\n            (quant_offset & 0xF)\n            .to(tl.uint16)\n            .to(tl.float16, bitcast=True)\n            .to(tl.float32)\n        )\n        quant_offset = quant_offset * 32768.0\n        scale_512 = scale * 512\n\n        dequant = quant_offset * scale_512 + shift\n    return dequant\n\n\n@triton.jit\ndef dequantize(\n    x_,\n    scale,\n    shift,\n    PACKED_PER_VAL: tl.constexpr,\n    IS_HIP: tl.constexpr,\n):\n    \"\"\"PACKED_PER_VAL is the number of values packed into each element x_.\n    For example, for int4 quantization and x_ of type int32, PACKED_PER_VAL is 8.\n    \"\"\"\n    # x_ : (BLOCK_N, D // PACKED_PER_VAL)\n    # scale: (BLOCK_N, 1)\n    # offsets: (PACKED_PER_VAL,)\n    BLOCK_N: tl.constexpr = x_.shape[0]\n    BLOCK_DMODEL_PACKED: tl.constexpr = x_.shape[1]\n    offsets = tl.arange(0, PACKED_PER_VAL) * (32 // PACKED_PER_VAL)\n    quant_offset = (\n        x_[:, :, None, :] >> offsets\n    )  # (BLOCK_N, D // PACKED_PER_VAL, PACKED_PER_VAL)\n\n    quant_offset = tl.reshape(\n        quant_offset, (BLOCK_N, BLOCK_DMODEL_PACKED * PACKED_PER_VAL)\n    )\n    if PACKED_PER_VAL == 4:\n        # FP8 quantization.\n        fp8_type = tl.float8e4b8 if torch.version.hip is not None else tl.float8e4nv\n        dequant = (\n            quant_offset.to(tl.uint8).to(fp8_type, bitcast=True).to(scale.dtype) * scale\n            + shift\n        )\n    else:\n        # Int4 quantization.\n        # Trick - instead of converting int4 to float16 we view it as float16\n        # and then multiply by 32768 * 512 == 2**24\n        if IS_HIP:\n            # Do final math in float32 to avoid casting to bf16 on MI300. There\n            # no direct instructions for this so its less performant on this workload.\n            quant_offset = (\n                (quant_offset & 0xF)\n                .to(tl.uint16)\n                .to(tl.float16, bitcast=True)\n                .to(tl.float32)\n            )\n            quant_offset = quant_offset * 32768.0\n        else:\n            quant_offset = (\n                (quant_offset & 0xF).to(tl.uint16).to(tl.float16, bitcast=True)\n            )\n            quant_offset = (quant_offset * 32768.0).to(tl.float16)\n        scale_512 = scale * 512\n\n        dequant = quant_offset * scale_512 + shift\n    return dequant\n\n\n@triton.jit\ndef _splitK_reduce(\n    Out_splitK,  # [B, G, H, split_k, Mq, K]\n    LSE_splitK,  # [B, G, H, split_k, Mq]\n    Out,  # [B, H, M, K]\n    LSE,  # [B, H, M]\n    split_k: tl.constexpr,\n    splitK_pow2: tl.constexpr,\n    stride_osk_z: tl.constexpr,\n    stride_osk_g: tl.constexpr,\n    stride_osk_h: tl.constexpr,\n    stride_osk_s: tl.constexpr,\n    stride_osk_m: tl.constexpr,\n    stride_osk_k: tl.constexpr,\n    stride_lsek_z: tl.constexpr,\n    stride_lsek_g: tl.constexpr,\n    stride_lsek_h: tl.constexpr,\n    stride_lsek_s: tl.constexpr,\n    stride_lsek_m: tl.constexpr,\n    stride_oz: tl.constexpr,\n    stride_og: tl.constexpr,\n    stride_oh: tl.constexpr,\n    stride_om: tl.constexpr,\n    stride_ok: tl.constexpr,\n    stride_lse_z: tl.constexpr,\n    stride_lse_g: tl.constexpr,\n    stride_lse_h: tl.constexpr,\n    stride_lse_m: tl.constexpr,\n    head_dim: tl.constexpr,\n    head_dim_pow_2: tl.constexpr,\n    H: tl.constexpr,\n    G: tl.constexpr,\n    WRITE_LSE: tl.constexpr,\n):\n    # grid = (M, B * G * H, 1)\n    off_m = tl.program_id(0).to(tl.int64)\n    off_zhg = tl.program_id(1).to(tl.int64)\n    off_z = off_zhg // (H * G)\n    off_h = (off_zhg // G) % H\n    off_g = off_zhg % G\n\n    head_dim_mask = tl.arange(0, head_dim_pow_2) < head_dim\n\n    Out_splitK_ptr = (\n        Out_splitK\n        + stride_osk_z * off_z\n        + stride_osk_g * off_g\n        + stride_osk_h * off_h\n        + stride_osk_m * off_m\n        + tl.arange(0, head_dim_pow_2)[None, :]\n        + stride_osk_s * tl.arange(0, splitK_pow2)[:, None]\n    )\n\n    LSE_splitK_ptr0 = (\n        LSE_splitK\n        + stride_lsek_z * off_z\n        + stride_lsek_g * off_g\n        + stride_lsek_h * off_h\n        + stride_lsek_m * off_m\n        + stride_lsek_s * tl.arange(0, splitK_pow2)\n    )\n\n    if splitK_pow2 > split_k:\n        mask_1d = tl.arange(0, splitK_pow2) < split_k\n        mask_2d = mask_1d[:, None] & head_dim_mask[None, :]\n        lse_splitk = tl.load(LSE_splitK_ptr0, mask=mask_1d, other=float(\"-inf\"))\n        lse_max = tl.max(lse_splitk)\n        out_splitk = tl.load(\n            Out_splitK_ptr, mask=mask_2d, other=0\n        )  # (split_k, head_dim_pow_2)\n        lse_splitk = tl.load(\n            LSE_splitK_ptr0, mask=mask_1d, other=float(\"-inf\")\n        )  # (split_k,)\n    else:\n        lse_splitk = tl.load(LSE_splitK_ptr0)\n        lse_max = tl.max(lse_splitk)\n        out_splitk = tl.load(Out_splitK_ptr)\n        lse_splitk = tl.load(LSE_splitK_ptr0)\n\n    sumexp_normalized_splitk = tl.math.exp2(\n        (lse_splitk - lse_max).to(tl.float32) * 1.44269504\n    )  # (split_k,)\n    sumexp_normalized = tl.sum(sumexp_normalized_splitk, axis=0)  # scalar\n    # Compute numerator\n    numerator_normalized = tl.sum(\n        out_splitk * sumexp_normalized_splitk[:, None], axis=0\n    )\n    acc = numerator_normalized / sumexp_normalized\n    acc = tl.where(lse_max == float(\"-inf\"), 0.0, acc)\n\n    Out_ptr = (\n        Out\n        + stride_oz * off_z\n        + stride_oh * off_h\n        + stride_og * off_g\n        + stride_om * off_m\n        + tl.arange(0, head_dim_pow_2)\n    )\n    if acc.dtype is tl.float64 and Out.dtype.element_ty is not tl.float64:\n        # must avoid direct cast f64->f16\n        acc = acc.to(tl.float32)\n    tl.store(Out_ptr, acc, mask=head_dim_mask)\n\n    if WRITE_LSE:\n        l_ptrs = (\n            LSE\n            + off_z * stride_lse_z\n            + off_g * stride_lse_g\n            + off_h * stride_lse_h\n            + off_m * stride_lse_m\n        )\n        to_store = lse_max + tl.math.log2(sumexp_normalized) / 1.44269504\n        to_store = tl.where(lse_max == float(\"-inf\"), lse_max, to_store)\n        tl.store(l_ptrs, to_store)\n\n\n@triton.jit\ndef _splitK_reduce_varargs(\n    Out_splitK: \"VAR_ARGS_ARRAY\",  # list of [B, G, H, Mq, K];\n    LSE_splitK: \"VAR_ARGS_ARRAY\",  # list of [B, G, H, Mq]\n    Out,  # [B, G, H, M, K]\n    LSE,  # [B, G, H, M]\n    stride_osk_z: \"VAR_ARGS_ARRAY\",\n    stride_osk_g: \"VAR_ARGS_ARRAY\",\n    stride_osk_h: \"VAR_ARGS_ARRAY\",\n    stride_osk_m: \"VAR_ARGS_ARRAY\",\n    stride_osk_k: \"VAR_ARGS_ARRAY\",\n    stride_lsek_z: \"VAR_ARGS_ARRAY\",\n    stride_lsek_g: \"VAR_ARGS_ARRAY\",\n    stride_lsek_h: \"VAR_ARGS_ARRAY\",\n    stride_lsek_m: \"VAR_ARGS_ARRAY\",\n    stride_oz,\n    stride_og,\n    stride_oh,\n    stride_om,\n    stride_ok,\n    stride_lse_z,\n    stride_lse_g,\n    stride_lse_h,\n    stride_lse_m,\n    head_dim: tl.constexpr,\n    head_dim_pow_2: tl.constexpr,\n    H: tl.constexpr,\n    G: tl.constexpr,\n    WRITE_LSE: tl.constexpr,\n):\n    \"\"\"\n    This version of reduce kernel takes attention and LSE of chunks as lists of tensors,\n    as opposed to _splitK_reduce, which takes each as a stacked tensor.\n    \"\"\"\n    # grid = (M, B * G * H, 1)\n    off_m = tl.program_id(0).to(tl.int64)\n    off_zhg = tl.program_id(1).to(tl.int64)\n    off_z = off_zhg // (H * G)\n    off_h = (off_zhg // G) % H\n    off_g = off_zhg % G\n    head_dim_mask = tl.arange(0, head_dim_pow_2) < head_dim\n\n    out_splitk_offset: \"VAR_ARGS_ARRAY\"  # noqa: F821\n    for i in range(len(Out_splitK)):\n        out_splitk_offset[i] = (  # noqa: F821\n            stride_osk_z[i] * off_z  # type: ignore # noqa: F821\n            + stride_osk_g[i] * off_g\n            + stride_osk_h[i] * off_h\n            + stride_osk_m[i] * off_m\n            + tl.arange(0, head_dim_pow_2)\n        )\n    lse_splitk_offset: \"VAR_ARGS_ARRAY\"  # noqa: F821\n    for i in range(len(Out_splitK)):\n        lse_splitk_offset[i] = (  # noqa: F821\n            stride_lsek_z[i] * off_z  # type: ignore # noqa: F821\n            + stride_lsek_g[i] * off_g\n            + stride_lsek_h[i] * off_h\n            + stride_lsek_m[i] * off_m\n        )\n\n    lse_max = float(\"-inf\")\n    for split_k_idx in range(len(Out_splitK)):  # type: ignore # noqa: F821\n        LSE_splitK_ptr = LSE_splitK[split_k_idx] + lse_splitk_offset[split_k_idx]  # type: ignore # noqa: F821\n        lse_splitk = tl.load(LSE_splitK_ptr)\n        lse_max = tl.maximum(lse_max, lse_splitk)\n\n    sumexp_normalized = 0.0\n    numerator_normalized = tl.zeros([head_dim_pow_2], dtype=tl.float32)\n\n    for split_k_idx in range(len(Out_splitK)):  # type: ignore # noqa: F821\n        out_splitk = tl.load(\n            Out_splitK[split_k_idx] + out_splitk_offset[split_k_idx], mask=head_dim_mask  # type: ignore # noqa: F821\n        )\n        lse_splitk = tl.load(LSE_splitK[split_k_idx] + lse_splitk_offset[split_k_idx])  # type: ignore # noqa: F821\n        # Compute denominator\n        sumexp_normalized_splitk = tl.math.exp2(\n            (lse_splitk - lse_max).to(tl.float32) * 1.44269504\n        )\n        sumexp_normalized += sumexp_normalized_splitk\n\n        # Compute numerator\n        numerator_normalized += out_splitk * sumexp_normalized_splitk\n\n    acc = numerator_normalized / sumexp_normalized\n    acc = tl.where(lse_max == float(\"-inf\"), 0.0, acc)\n\n    Out_ptr = (\n        Out\n        + stride_oz * off_z\n        + stride_oh * off_h\n        + stride_og * off_g\n        + stride_om * off_m\n        + tl.arange(0, head_dim_pow_2)\n    )\n    if acc.dtype is tl.float64 and Out.dtype.element_ty is not tl.float64:\n        # must avoid direct cast f64->f16\n        acc = acc.to(tl.float32)\n    tl.store(Out_ptr, acc, mask=head_dim_mask)\n\n    if WRITE_LSE:\n        l_ptrs = (\n            LSE\n            + off_z * stride_lse_z\n            + off_g * stride_lse_g\n            + off_h * stride_lse_h\n            + off_m * stride_lse_m\n        )\n        to_store = lse_max + tl.math.log2(sumexp_normalized) / 1.44269504\n        to_store = tl.where(lse_max == float(\"-inf\"), lse_max, to_store)\n        tl.store(l_ptrs, to_store)\n\n\n@triton.jit\ndef _splitK_reduce_varargs_backward(\n    Out_splitK: \"VAR_ARGS_ARRAY\",  # list of [B, G, H, Mq, K];\n    LSE_splitK: \"VAR_ARGS_ARRAY\",  # list of [B, G, H, Mq]\n    Dout_splitK: \"VAR_ARGS_ARRAY\",  # gradients - same shape as the inputs themselves\n    DLSE_splitK: \"VAR_ARGS_ARRAY\",\n    Out,  # [B, G, H, M, K]\n    LSE,  # [B, G, H, M]\n    DOut,\n    DLSE,\n    # strides of chunked inputs: attention and LSE\n    stride_osk_z: \"VAR_ARGS_ARRAY\",\n    stride_osk_g: \"VAR_ARGS_ARRAY\",\n    stride_osk_h: \"VAR_ARGS_ARRAY\",\n    stride_osk_m: \"VAR_ARGS_ARRAY\",\n    stride_osk_k: \"VAR_ARGS_ARRAY\",\n    stride_lsek_z: \"VAR_ARGS_ARRAY\",\n    stride_lsek_g: \"VAR_ARGS_ARRAY\",\n    stride_lsek_h: \"VAR_ARGS_ARRAY\",\n    stride_lsek_m: \"VAR_ARGS_ARRAY\",\n    # strides of merged outputs: attention and LSE\n    stride_oz,\n    stride_og,\n    stride_oh,\n    stride_om,\n    stride_ok,\n    stride_lse_z,\n    stride_lse_g,\n    stride_lse_h,\n    stride_lse_m,\n    # strides of gradients\n    stride_doz,\n    stride_dog,\n    stride_doh,\n    stride_dom,\n    stride_dok,\n    stride_dlse_z,\n    stride_dlse_g,\n    stride_dlse_h,\n    stride_dlse_m,\n    BLOCK_SIZE: tl.constexpr,\n    H: tl.constexpr,\n    G: tl.constexpr,\n):\n    \"\"\"\n    Backward for _splitK_reduce_varargs. Similar to forward, it takes\n    attention and LSE of chunks as lists of tensors,\n    and outputs the corresponding gradients in the same format.\n    \"\"\"\n\n    # grid = (M, B * G * H, 1)\n    off_m = tl.program_id(0).to(tl.int64)\n    off_zhg = tl.program_id(1).to(tl.int64)\n    off_z = off_zhg // (H * G)\n    off_h = (off_zhg // G) % H\n    off_g = off_zhg % G\n\n    # Compute offsets inside each attention/LSE chunk.\n    # Note that each chunk can have different strides, so offsets can also be different.\n    out_splitk_offset: \"VAR_ARGS_ARRAY\"  # noqa: F821\n    for i in range(len(Out_splitK)):\n        out_splitk_offset[i] = (  # type: ignore # noqa: F821\n            stride_osk_z[i] * off_z\n            + stride_osk_g[i] * off_g\n            + stride_osk_h[i] * off_h\n            + stride_osk_m[i] * off_m\n            + tl.arange(0, BLOCK_SIZE)\n        )\n    lse_splitk_offset: \"VAR_ARGS_ARRAY\"  # noqa: F821\n    for i in range(len(Out_splitK)):\n        lse_splitk_offset[i] = (  # type: ignore # noqa: F821\n            stride_lsek_z[i] * off_z\n            + stride_lsek_g[i] * off_g\n            + stride_lsek_h[i] * off_h\n            + stride_lsek_m[i] * off_m\n        )\n\n    lse_max = float(\"-inf\")\n    for split_k_idx in range(len(Out_splitK)):  # type: ignore # noqa: F821\n        LSE_splitK_ptr = LSE_splitK[split_k_idx] + lse_splitk_offset[split_k_idx]  # type: ignore # noqa: F821\n        lse_splitk = tl.load(LSE_splitK_ptr)\n        lse_max = tl.maximum(lse_max, lse_splitk)\n\n    # Load attention and the corresponding gradient\n    offset_out = (\n        stride_oz * off_z\n        + stride_oh * off_h\n        + stride_og * off_g\n        + stride_om * off_m\n        + tl.arange(0, BLOCK_SIZE)\n    )\n    offset_dout = (\n        stride_doz * off_z\n        + stride_doh * off_h\n        + stride_dog * off_g\n        + stride_dom * off_m\n        + tl.arange(0, BLOCK_SIZE)\n    )\n    out = tl.load(Out + offset_out)\n    dattn = tl.load(DOut + offset_dout)\n\n    # Load LSE and the corresponding gradient\n    offset_lse = (\n        stride_lse_z * off_z\n        + stride_lse_h * off_h\n        + stride_lse_g * off_g\n        + stride_lse_m * off_m\n    )\n    offset_dlse = (\n        stride_dlse_z * off_z\n        + stride_dlse_h * off_h\n        + stride_dlse_g * off_g\n        + stride_dlse_m * off_m\n    )\n    lse = tl.load(LSE + offset_lse)\n    dlse = tl.load(DLSE + offset_dlse)\n\n    for split_k_idx in range(len(Out_splitK)):  # type: ignore # noqa: F821\n        # Load attention and LSE of chunks\n        out_splitk = tl.load(Out_splitK[split_k_idx] + out_splitk_offset[split_k_idx])  # type: ignore # noqa: F821\n        lse_splitk = tl.load(LSE_splitK[split_k_idx] + lse_splitk_offset[split_k_idx])  # type: ignore # noqa: F821\n\n        # Pointers to save gradients of attention and LSE of chunks\n        dout_splitk_ptr = Dout_splitK[split_k_idx] + out_splitk_offset[split_k_idx]  # type: ignore # noqa: F821\n        dlse_splitk_ptr = DLSE_splitK[split_k_idx] + lse_splitk_offset[split_k_idx]  # type: ignore # noqa: F821\n\n        # dX/dattn_i = dX/dattn * dattn/dattn_i + dX/dlse * dlse/dattn_i, and dlse/dattn_i == 0\n        dattn_dattn_i = tl.exp(lse_splitk - lse_max) / tl.exp(lse - lse_max)\n        dX_dattn_i = dattn_dattn_i * dattn\n        tl.store(dout_splitk_ptr, dX_dattn_i)\n\n        dattn_dlse_i = (out_splitk - out) * dattn_dattn_i\n\n        # dX/dlse_i = dX/dattn * dattn/dlse_i + dX/dlse * dlse/dlse_i\n        dlse_dlse_i = dattn_dattn_i\n        dX_dlse_i = dlse_dlse_i * dlse + tl.sum(\n            dattn_dlse_i * dattn\n        )  # Sum is over the hidden dimension\n        tl.store(dlse_splitk_ptr, dX_dlse_i)\n"
  },
  {
    "path": "xformers/ops/fmha/attn_bias.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\"\"\"\nThis file contains biases that can be used as the `attn_bias` argument in\n:attr:`xformers.ops.memory_efficient_attention`.\nEssentially, a bias is a Tensor which will be added to the ``Q @ K.t`` before\ncomputing the ``softmax``.\n\n\nThe goal of having custom made classes (instead of dense tensors) is that\nwe want to avoid having to load the biases from memory in the kernel, for\nperformance reasons. We also want to be able to know before-hand which\nparts of the attention matrix we will need to compute (eg causal masks).\n\n\nSome very common biases are LowerTriangularMask and BlockDiagonalMask.\n\"\"\"\n\nimport math\nfrom dataclasses import dataclass\nfrom typing import (\n    Any,\n    cast,\n    ClassVar,\n    Iterable,\n    List,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    Union,\n)\n\nimport torch\n\n\ndef _to_device(t: torch.Tensor, device: torch.device) -> torch.Tensor:\n    if t.device == device:\n        return t\n    if device == torch.device(\"cpu\"):\n        return t.to(device)\n\n    return t.to(device, non_blocking=True)\n\n\ndef _to_device_tensor(seq: Sequence[int], dtype: torch.dtype, device: torch.device):\n    if device == torch.device(\"cpu\"):\n        return torch.tensor(seq, dtype=dtype)\n\n    return torch.tensor(seq, dtype=dtype, pin_memory=True).to(device, non_blocking=True)\n\n\nclass AttentionBias:\n    \"\"\"Base class for a custom bias that can be applied \\\n        as the attn_bias argument in\n    :attr:`xformers.ops.memory_efficient_attention`.\n\n    That function has the ability to add a tensor, the\n    attention bias, to the QK^T matrix before it is used\n    in the softmax part of the attention calculation.\n    The attention bias tensor with shape\n    (B or 1, n_queries, number of keys)\n    can be given as the attn_bias input.\n    The most common use case is for an attention bias is\n    to contain only zeros and negative infinities, which forms\n    a mask so that some queries only attend to some keys.\n\n    Children of this class define alternative things which can\n    be used as the attn_bias input to define an attention bias which\n    forms such a mask, for some common cases.\n\n    When using an :attr:`xformers.ops.AttentionBias`\n    instead of a :attr:`torch.Tensor`, the mask matrix does\n    not need to be materialized, and can be\n    hardcoded into some kernels for better performance.\n\n    See:\n\n    - :attr:`xformers.ops.fmha.attn_bias.LowerTriangularMask`\n    - :attr:`xformers.ops.fmha.attn_bias.LowerTriangularFromBottomRightMask`\n    - :attr:`xformers.ops.fmha.attn_bias.LowerTriangularMaskWithTensorBias`\n    - :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalMask`\n    - :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalMask`\n\n    \"\"\"\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"\n        Materializes the bias as a `torch.Tensor`. This is very slow\n        and we don't attempt to make it fast. Only use for debugging/testing.\n\n        Shape should be like `[*, q_seqlen, k_seqlen]`\n        \"\"\"\n        raise NotImplementedError()\n\n\ndef _get_default_bias_device(device: Optional[torch.device] = None) -> torch.device:\n    if device is None:\n        if torch.cuda.is_available():\n            return torch.device(\"cuda\")\n        if torch.mtia.is_available():\n            return torch.device(\"mtia\")\n        return torch.device(\"cpu\")\n    return device\n\n\ndef _materialize_causal_mask(\n    shape: Tuple[int, ...],\n    dtype: torch.dtype = torch.float32,\n    device: Union[str, torch.device] = \"cpu\",\n    *,\n    window_size: Optional[int] = None,\n    from_bottomright: bool = False,\n) -> torch.Tensor:\n    create_as = dtype if dtype is not torch.bfloat16 else torch.float32\n    tensor = torch.full(  # type: ignore\n        shape,\n        dtype=create_as,\n        fill_value=1,\n        device=device,\n    )\n\n    num_queries, num_keys = shape[-2:]\n    shift = 0\n    if from_bottomright:\n        shift = num_keys - num_queries\n\n    mask = torch.tril(tensor, diagonal=shift).to(dtype)  # type: ignore\n    if window_size is not None:\n        mask = torch.triu(mask, diagonal=shift - window_size + 1)\n    mask = torch.log(mask)\n    return mask.to(dtype)\n\n\nclass LowerTriangularMask(AttentionBias):\n    \"\"\"\n    A lower-triangular (aka causal) mask\n\n    A query Q cannot attend to a key which is farther from the\n    initial key than Q is from the initial query.\n\n    See also :attr:`LowerTriangularFromBottomRightMask` if the number\n    of queries is not equal to the number of keys/values.\n    \"\"\"\n\n    def __init__(self, device: Union[torch.device, None] = None) -> None:\n        \"\"\"not used, only here for backward compatibility\"\"\"\n\n    def to(self, device: torch.device) -> \"LowerTriangularMask\":\n        assert type(self) is LowerTriangularMask, \"Please implement in subclass\"\n        return self\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(shape, dtype=dtype, device=device)\n\n    def add_bias(self, bias: torch.Tensor) -> \"LowerTriangularMaskWithTensorBias\":\n        \"\"\"\n        Creates a new causal mask with an arbitrary ``torch.Tensor`` bias\n        \"\"\"\n        return LowerTriangularMaskWithTensorBias(bias)\n\n\n@dataclass\nclass LocalAttentionFromBottomRightMask(AttentionBias):\n    \"\"\"\n    A local attention mask\n\n    The query at position :math:`q` can attend the key at position :math:`k` if\n    :math:`q - window\\\\_left <= k + s <= q + window\\\\_right`\n\n    With :math:`s = num\\\\_queries - num\\\\_keys`\n\n    :Example:\n\n    .. code-block:: python\n\n        import torch\n        from xformers.ops import fmha\n\n        bias = fmha.attn_bias.LocalAttentionFromBottomRightMask(window_left=1, window_right=2)\n        print(bias.materialize(shape=(4, 4)).exp())\n        print(bias.materialize(shape=(4, 5)).exp())\n\n    .. code-block:: text\n\n        # 4x4\n        tensor([[1., 1., 1., 0.],\n                [1., 1., 1., 1.],\n                [0., 1., 1., 1.],\n                [0., 0., 1., 1.]])\n\n        # 4x5\n        tensor([[1., 1., 1., 1., 0.],\n                [0., 1., 1., 1., 1.],\n                [0., 0., 1., 1., 1.],\n                [0., 0., 0., 1., 1.]])\n\n    :Illustration:\n\n    .. figure:: /_static/local_attn.png\n        :width: 240px\n\n        The total window size is :math:`window\\\\_left + 1 + window\\\\_right`\n    \"\"\"\n\n    window_left: int\n    window_right: int\n\n    def to(self, device) -> \"LocalAttentionFromBottomRightMask\":\n        return self\n\n    def __post_init__(self) -> None:\n        if self.window_left < 0:\n            raise ValueError(\n                \"Invalid window value passed to \"\n                \"`LocalAttentionFromBottomRightMask`: expected\"\n                f\"`window_left > 0` but got window_left={self.window_left}\"\n            )\n        if self.window_right < 0:\n            raise ValueError(\n                \"Invalid window value passed to \"\n                \"`LocalAttentionFromBottomRightMask`: expected\"\n                f\"`window_right > 0` but got window_right={self.window_right}\"\n            )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        create_as = dtype if dtype is not torch.bfloat16 else torch.float32\n        mask = torch.full(  # type: ignore\n            shape,\n            dtype=create_as,\n            fill_value=1,\n            device=device,\n        )\n\n        num_queries, num_keys = shape[-2:]\n        shift = num_keys - num_queries\n\n        mask = torch.triu(mask, diagonal=shift - self.window_left)\n        mask = torch.tril(mask, diagonal=shift + self.window_right)\n        mask = torch.log(mask)\n        return mask.to(dtype)\n\n\nclass LowerTriangularFromBottomRightMask(AttentionBias):\n    \"\"\"\n    A causal masking.\n\n    This mask is exactly the same as :attr:`LowerTriangularMask` when there is\n    the same number of queries and keys.\n    When the number of queries is different from the number of keys,\n    it is a triangular mask shifted so that the last query can attend to\n    the last key.\n    In other words, a query Q cannot attend to a key which is nearer the\n    final key than Q is to the final query.\n\n\n    .. figure:: /_static/causal_bottom_right.png\n\n        The difference between :attr:`LowerTriangularMask` (left) and\n        :attr:`LowerTriangularFromBottomRightMask` (right). They become\n        equivalent if the number of queries equals the number of keys.\n    \"\"\"\n\n    def to(self, device: torch.device) -> \"LowerTriangularFromBottomRightMask\":\n        assert (\n            type(self) is LowerTriangularFromBottomRightMask\n        ), \"Please implement in subclass\"\n        return self\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(\n            shape, dtype=dtype, device=device, from_bottomright=True\n        )\n\n    def make_local_attention(\n        self, window_size: int\n    ) -> \"LowerTriangularFromBottomRightLocalAttentionMask\":\n        \"\"\"\n        Create a new bias which combines local + causal attention.\n\n        See :attr:`LowerTriangularFromBottomRightLocalAttentionMask`\n        \"\"\"\n        return LowerTriangularFromBottomRightLocalAttentionMask(window_size)\n\n\n@dataclass\nclass LowerTriangularFromBottomRightLocalAttentionMask(\n    LowerTriangularFromBottomRightMask\n):\n    \"\"\"\n    A mask that combines both :attr:`LowerTriangularFromBottomRightMask` and\n    local attention.\n\n    A query whose distance from the final query is X cannot attend to a key\n    whose distance to the final key is either of:\n\n    * less than X (i.e. \"causal attention\", same as :attr:`LowerTriangularFromBottomRightMask`)\n    * greater than or equal to X + window_size (i.e. \"local attention\")\n\n\n    .. figure:: /_static/causal_bottom_right_local.png\n\n        The mask from :attr:`LowerTriangularFromBottomRightLocalAttentionMask`.\n        The green area is calculated, and the grey area is masked out.\n    \"\"\"\n\n    _window_size: int\n\n    def to(\n        self, device: torch.device\n    ) -> \"LowerTriangularFromBottomRightLocalAttentionMask\":\n        assert (\n            type(self) is LowerTriangularFromBottomRightLocalAttentionMask\n        ), \"Please implement in subclass\"\n        return self\n\n    def __post_init__(self) -> None:\n        if self._window_size <= 0:\n            raise ValueError(\n                f\"Expected `window_size > 0`, but window_size={self._window_size}\"\n            )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(\n            shape,\n            dtype=dtype,\n            device=device,\n            window_size=self._window_size,\n            from_bottomright=True,\n        )\n\n\nclass LowerTriangularMaskWithTensorBias(LowerTriangularMask):\n    \"\"\"A lower-triangular (aka causal) mask with an additive bias\"\"\"\n\n    def __init__(self, bias: torch.Tensor) -> None:\n        self._bias = bias\n\n    def to(self, device: torch.device) -> \"LowerTriangularMaskWithTensorBias\":\n        assert (\n            type(self) is LowerTriangularMaskWithTensorBias\n        ), \"Please implement in subclass\"\n        return LowerTriangularMaskWithTensorBias(_to_device(self._bias, device))\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return super().materialize(shape, dtype=dtype, device=device) + self._bias\n\n\n@dataclass\nclass _SeqLenInfo:\n    \"\"\"\n    (Internal) Represents the division of a dimension into blocks.\n\n    For example, to represents a dimension of length 7 divided into\n    three blocks of lengths 2, 3 and 2, use `from_seqlength([2, 3, 2])`.\n    The members will be:\n        max_seqlen: 3\n        min_seqlen: 2\n        seqstart_py: [0, 2, 5, 7]\n        seqstart: torch.IntTensor([0, 2, 5, 7])\n    \"\"\"\n\n    seqstart: torch.Tensor\n    max_seqlen: int\n    min_seqlen: int\n    seqstart_py: List[int]\n\n    def to(self, device: torch.device) -> \"_SeqLenInfo\":\n        assert type(self) is _SeqLenInfo, \"Please implement in subclass\"\n        if self.seqstart.device == device:\n            return self\n        return _SeqLenInfo(\n            seqstart=_to_device(self.seqstart, device),\n            max_seqlen=self.max_seqlen,\n            min_seqlen=self.min_seqlen,\n            seqstart_py=self.seqstart_py,\n        )\n\n    def intervals(self) -> Iterable[Tuple[int, int]]:\n        yield from zip(self.seqstart_py, self.seqstart_py[1:])\n\n    @classmethod\n    def _get_seqstart(\n        cls, seqlens: Iterable[int], *, device: torch.device\n    ) -> Tuple[int, int, List[int], torch.Tensor]:\n        \"\"\"\n        Given sequence lengths, returns the min/max value and the sequence start\n        positions (offsets), with first element being 0 (returned in list and Tensor).\n        \"\"\"\n\n        assert not isinstance(seqlens, torch.Tensor)\n        seqstart_py = [0]\n        max_seqlen = -1\n        min_seqlen = -1\n        for seqlen in seqlens:\n            min_seqlen = min(min_seqlen, seqlen) if min_seqlen != -1 else seqlen\n            max_seqlen = max(max_seqlen, seqlen)\n            seqstart_py.append(seqstart_py[len(seqstart_py) - 1] + seqlen)\n        seqstart = _to_device_tensor(seqstart_py, dtype=torch.int32, device=device)\n\n        return (min_seqlen, max_seqlen, seqstart_py, seqstart)\n\n    @classmethod\n    def from_seqlens(\n        cls, seqlens: Iterable[int], *, device: Optional[torch.device] = None\n    ) -> \"_SeqLenInfo\":\n        \"\"\"\n        Input tensors are assumed to be in shape [B, M, *]\n        \"\"\"\n        device = _get_default_bias_device(device)\n        min_seqlen, max_seqlen, seqstart_py, seqstart = cls._get_seqstart(\n            seqlens, device=device\n        )\n\n        return cls(\n            max_seqlen=max_seqlen,\n            min_seqlen=min_seqlen,\n            seqstart=seqstart,\n            seqstart_py=seqstart_py,\n        )\n\n    def from_seqlens_inplace(self, seqlens: Iterable[int]) -> None:\n        \"\"\"\n        Perform in-place update. You can only update with the same shape.\n        Can be useful with CUDA graphs.\n        \"\"\"\n        min_seqlen, max_seqlen, seqstart_py, seqstart = self._get_seqstart(\n            seqlens, device=self.seqstart.device\n        )\n\n        assert len(seqstart_py) == len(self.seqstart_py), (\n            f\"Old / New len {len(self.seqstart_py)} / {len(seqstart)}, \"\n            f\"Contents {self.seqstart_py} / {seqstart}\"\n        )\n        assert self.max_seqlen >= max_seqlen, (\n            f\"For inplace update, new max_seqlen {max_seqlen} \"\n            f\"cannot exceed the previous max_seqlen {self.max_seqlen}\"\n        )\n        for i in range(len(seqstart_py)):\n            self.seqstart_py[i] = seqstart_py[i]\n        self.seqstart.copy_(seqstart, non_blocking=True)\n\n    def split(\n        self, x: torch.Tensor, batch_sizes: Optional[Sequence[int]] = None\n    ) -> List[torch.Tensor]:\n        if self.seqstart_py[-1] != x.shape[1] or x.shape[0] != 1:\n            raise ValueError(\n                f\"Invalid `torch.Tensor` of shape {x.shape}, expected format \"\n                f\"(B, M, *) with B=1 and M={self.seqstart_py[-1]}\\n\"\n                f\" seqstart: {self.seqstart_py}\"\n            )\n        if batch_sizes is None:\n            batch_sizes = [1] * (len(self.seqstart_py) - 1)\n        split_chunks = []\n        it = 0\n        for batch_size in batch_sizes:\n            split_chunks.append(\n                self.seqstart_py[it + batch_size] - self.seqstart_py[it]\n            )\n            it += batch_size\n        return [\n            tensor.reshape([bs, -1, *tensor.shape[2:]])\n            for bs, tensor in zip(batch_sizes, x.split(split_chunks, dim=1))\n        ]\n\n\n@dataclass\nclass _PaddedSeqLenInfo(_SeqLenInfo):\n    \"\"\"\n    (Internal)  Represents the division of a dimension into blocks which are\n    padded out to the same total length.\n\n    For example, to represent a dimension of length 12 with space for\n    three blocks of length 4, but where the occupied lengths are\n    2, 3 and 2, use `from_seqlens_padded([2, 3, 2], 4)`.\n\n    The layout along the dimension is\n\n     0 ─►  block 0\n           block 0\n           <space>\n           <space>\n     4 ─►  block 1\n           block 1\n           block 1\n           <space>\n     8 ─►  block 2\n           block 2\n           <space>\n           <space>\n    12 ─►\n\n    The members will be:\n        max_seqlen: 3\n        min_seqlen: 2\n        seqstart_py: [0, 4, 8, 12]\n        seqstart: torch.IntTensor([0, 4, 8, 12])\n        seqlen_py: [2, 3, 2]\n        seqlen: torch.IntTensor([2, 3, 2])\n        padding: 4\n    \"\"\"\n\n    seqlen: torch.Tensor\n    seqlen_py: List[int]\n    padding: int\n    # From parent: seqstart[i] contains the start position\n    # of the i-th sequence\n    # seqstart: torch.Tensor\n\n    def __post_init__(self) -> None:\n        assert len(self.seqstart_py) == len(self.seqlen_py) + 1\n\n    def to(self, device: torch.device) -> \"_PaddedSeqLenInfo\":\n        assert type(self) is _PaddedSeqLenInfo, \"Please implement in subclass\"\n        if self.seqlen.device == device:\n            return self\n        return _PaddedSeqLenInfo(\n            # _SeqLenInfo\n            seqstart=_to_device(self.seqstart, device),\n            max_seqlen=self.max_seqlen,\n            min_seqlen=self.min_seqlen,\n            seqstart_py=self.seqstart_py,\n            # _PaddedSeqLenInfo\n            seqlen=_to_device(self.seqlen, device),\n            seqlen_py=self.seqlen_py,\n            padding=self.padding,\n        )\n\n    def intervals(self) -> Iterable[Tuple[int, int]]:\n        for (start, _), length in zip(super().intervals(), self.seqlen_py):\n            yield start, start + length\n\n    @classmethod\n    def from_seqlens(\n        cls, seqlens: Iterable[int], *, device: Optional[torch.device] = None\n    ) -> \"_SeqLenInfo\":\n        raise RuntimeError(\n            \"Use either `_SeqLenInfo.from_seqlens` or `_PaddedSeqLenInfo.from_seqlens_padded`\"\n        )\n\n    @classmethod\n    def from_seqlens_padded(\n        cls,\n        seqlens: Sequence[int],\n        padding: int,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"_PaddedSeqLenInfo\":\n        \"\"\"\n        Input tensors are assumed to be in shape [B, M, *]\n        seqstart = padding * torch.arange(batch_size)\n        \"\"\"\n        assert not isinstance(seqlens, torch.Tensor)\n        assert all(\n            seqlen <= padding for seqlen in seqlens\n        ), f\"Seqlens {seqlens} Padding {padding}\"\n        device = _get_default_bias_device(device)\n        seqstart_py = list(range(0, len(seqlens) * padding + 1, padding))\n        seqlen = _to_device_tensor(seqlens, dtype=torch.int32, device=device)\n        return cls(\n            seqlen=seqlen,\n            seqlen_py=seqlens if isinstance(seqlens, list) else list(seqlens),\n            max_seqlen=max(seqlens),\n            min_seqlen=min(seqlens),\n            seqstart=_to_device_tensor(seqstart_py, dtype=torch.int32, device=device),\n            seqstart_py=seqstart_py,\n            padding=padding,\n        )\n\n    def from_seqlens_padded_inplace(self, seqlens: Sequence[int]) -> None:\n        \"\"\"\n        Perform in-place update. You can only update with the same shape.\n        Can be useful with CUDA graphs.\n        Note: we don't update padding because they would have been already baked\n        into CUDA graphs during the generation.\n        \"\"\"\n        assert not isinstance(seqlens, torch.Tensor)\n        assert all(\n            seqlen <= self.padding for seqlen in seqlens\n        ), f\"Seqlens {seqlens} Padding {self.padding}\"\n        seqlen_tensor = torch.tensor(seqlens, dtype=torch.int32)\n\n        assert len(self.seqlen_py) == len(seqlens), (\n            f\"Old/New len {len(self.seqlen_py)} / {len(seqlens)}, \"\n            f\"Contents {self.seqlen_py} / {seqlens}\"\n        )\n        assert self.max_seqlen >= max(seqlens), (\n            f\"For inplace update, new max_seqlen {max(seqlens)} \"\n            f\"cannot exceed the previous max_seqlen {self.max_seqlen}\"\n        )\n\n        for i in range(len(self.seqlen_py)):\n            self.seqlen_py[i] = seqlens[i]\n\n        self.seqlen.copy_(seqlen_tensor, non_blocking=True)\n\n    def split(\n        self, x: torch.Tensor, batch_sizes: Optional[Sequence[int]] = None\n    ) -> List[torch.Tensor]:\n        raise NotImplementedError(\"_PaddedSeqLenInfo.split\")\n\n\n@dataclass\nclass _GappySeqInfo(_SeqLenInfo):\n    \"\"\"\n    (Internal) Flexible equivalent of _PaddedSeqLenInfo. There are two\n    distinct semantics.\n\n    (1) For non-paged masks:\n    Represents the division of a dimension into blocks which are\n    anywhere. Each just has a start and a length. The final start is the total\n    length of the dimension.\n\n    For example, to represent a dimension of length 14 like follows with\n    three occupied lengths of\n    6, 3 and 1, use `from_seqlens_padded([0, 7, 12, 14], [6, 3, 1])`.\n\n    The layout along the dimension is\n\n     0 ─►  block 0\n           block 0\n           block 0\n           block 0\n     4 ─►  block 0\n           block 0\n           <space>\n           block 1\n     8 ─►  block 1\n           block 1\n           <space>\n           <space>\n     12 ─► block 2\n           <space>\n\n    The members will be:\n        max_seqlen: 6\n        min_seqlen: 1\n        seqstart_py: [0, 7, 12, 14]\n        seqstart: torch.IntTensor([0, 7, 12, 14])\n        seqlen_py: [6, 3, 1]\n        seqlen: torch.IntTensor([6, 3, 1])\n\n    (2) For paged masks:\n    The notional space is divided into batch-size-many blocks.\n    seqstart and seqstart_py is an offset in the block, not in\n    the whole space, and the extra last element is not important.\n    And seqlen is the index of the last key in the block.\n    Otherwise as above.\n    \"\"\"\n\n    seqlen: torch.Tensor\n    seqlen_py: Sequence[int]\n    # From parent: seqstart[i] contains the start position\n    # of the i-th sequence\n    # seqstart: torch.Tensor\n\n    def to(self, device: torch.device) -> \"_GappySeqInfo\":\n        assert type(self) is _GappySeqInfo, \"Please implement in subclass\"\n        if self.seqlen.device == device:\n            return self\n        return _GappySeqInfo(\n            # _SeqLenInfo\n            seqstart=_to_device(self.seqstart, device),\n            max_seqlen=self.max_seqlen,\n            min_seqlen=self.min_seqlen,\n            seqstart_py=self.seqstart_py,\n            # _GappySeqInfo\n            seqlen=_to_device(self.seqlen, device),\n            seqlen_py=self.seqlen_py,\n        )\n\n    def intervals(self) -> Iterable[Tuple[int, int]]:\n        for (start, _), length in zip(super().intervals(), self.seqlen_py):\n            yield start, start + length\n\n    @classmethod\n    def from_seqlens(\n        cls, seqlens: Iterable[int], *, device: Optional[torch.device] = None\n    ) -> \"_SeqLenInfo\":\n        raise NotImplementedError()\n\n    @classmethod\n    def from_seqlens_gappy(\n        cls,\n        seqstarts: Sequence[int],\n        seqlens: Sequence[int],\n        paged: bool,\n        *,\n        device: torch.device,\n    ) -> \"_GappySeqInfo\":\n        assert not isinstance(seqlens, torch.Tensor)\n        seqstart_py = list(seqstarts)\n        if len(seqlens) == 0:\n            raise ValueError(\"No elements\")\n        if len(seqstarts) - len(seqlens) != 1:\n            raise ValueError(\n                f\"len(seqstarts)={seqstarts} should be len(seqlens)={seqlens}\"\n            )\n        max_seqlen = max(seqlens)\n        min_seqlen = min(seqlens)\n        if paged:\n            seqstart_py.append(-1)\n            seqlens = [i + j for i, j in zip(seqstart_py, seqlens)]\n        seqlen = _to_device_tensor(seqlens, dtype=torch.int32, device=device)\n        return cls(\n            seqlen=seqlen,\n            seqlen_py=seqlens,\n            max_seqlen=max_seqlen,\n            min_seqlen=min_seqlen,\n            seqstart=_to_device_tensor(seqstart_py, dtype=torch.int32, device=device),\n            seqstart_py=seqstart_py,\n        )\n\n    def split(\n        self, x: torch.Tensor, batch_sizes: Optional[Sequence[int]] = None\n    ) -> List[torch.Tensor]:\n        raise NotImplementedError(\"_GappySeqInfo.split\")\n\n\n@dataclass\nclass BlockDiagonalMask(AttentionBias):\n    \"\"\"\n    A block-diagonal mask that can be passed as ``attn_bias``\n    argument to :attr:`xformers.ops.memory_efficient_attention`.\n\n    Queries and Keys are each divided into the same number of blocks.\n    Queries in block i only attend to keys in block i.\n\n    .. figure:: /_static/block_diag_bias.png\n\n        This bias can be used to handle a batch of sequences of\n        different lengths, via :attr:`BlockDiagonalMask.from_tensor_list`\n\n    :Example:\n\n    .. code-block:: python\n\n        import torch\n        from xformers.ops import fmha\n\n        K = 16\n        dtype = torch.float16\n        device = \"cuda\"\n        list_x = [\n            torch.randn([1, 3, 1, K], dtype=dtype, device=device),\n            torch.randn([1, 6, 1, K], dtype=dtype, device=device),\n            torch.randn([1, 2, 1, K], dtype=dtype, device=device),\n        ]\n        attn_bias, x = fmha.BlockDiagonalMask.from_tensor_list(list_x)\n        linear = torch.nn.Linear(K, K * 3).to(device=device, dtype=dtype)\n\n        q, k, v = linear(x).reshape([1, -1, 1, 3, K]).unbind(-2)\n        out = fmha.memory_efficient_attention(q, k, v, attn_bias=attn_bias)\n        list_out = attn_bias.split(out)\n        print(list_out[0].shape)  # [1, 3, 1, K]\n        assert tuple(list_out[0].shape) == (1, 3, 1, K)\n\n    \"\"\"\n\n    q_seqinfo: _SeqLenInfo\n    k_seqinfo: _SeqLenInfo\n    _batch_sizes: Optional[Sequence[int]] = None\n\n    def to(self, device) -> \"BlockDiagonalMask\":\n        assert type(self) is BlockDiagonalMask, \"Please implement in subclass\"\n        return BlockDiagonalMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _batch_sizes=self._batch_sizes,\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return torch.zeros(\n            shape,\n            dtype=dtype,\n            device=device,\n        )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        assert shape[-1] == self.k_seqinfo.seqstart_py[-1], (\n            shape[-1],\n            self.k_seqinfo.seqstart_py[-1],\n        )\n        assert shape[-2] == self.q_seqinfo.seqstart_py[-1], (\n            shape[-2],\n            self.q_seqinfo.seqstart_py[-1],\n        )\n        mask = torch.empty(shape[-2:], dtype=dtype, device=device)\n        mask.fill_(-math.inf)\n        for i, ((q_start, q_end), (k_start, k_end)) in enumerate(\n            zip(\n                self.q_seqinfo.intervals(),\n                self.k_seqinfo.intervals(),\n            )\n        ):\n            mask[q_start:q_end, k_start:k_end] = self._create_block_mask(\n                (q_end - q_start, k_end - k_start),\n                dtype=dtype,\n                device=device,\n            )\n        for _ in range(len(shape) - 2):\n            mask = mask.unsqueeze(0)\n        return mask.expand(shape)\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_seqlen: Optional[Sequence[int]] = None,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"BlockDiagonalMask\":\n        \"\"\"Creates a :attr:`BlockDiagonalMask` from a list of tensors lengths for query and key/value.\n\n        Args:\n            q_seqlen (Union[Sequence[int], torch.Tensor]): List or tensor of sequence lengths for query tensors\n            kv_seqlen (Union[Sequence[int], torch.Tensor], optional): List or tensor of sequence lengths for key/value.\n                    (Defaults to ``q_seqlen``.)\n        Returns:\n            BlockDiagonalMask\n        \"\"\"\n        device = _get_default_bias_device(device)\n        assert kv_seqlen is None or len(q_seqlen) == len(kv_seqlen)\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        if kv_seqlen is None or q_seqlen == kv_seqlen:\n            k_seqinfo = q_seqinfo\n        else:\n            k_seqinfo = _SeqLenInfo.from_seqlens(kv_seqlen, device=device)\n        return cls(q_seqinfo=q_seqinfo, k_seqinfo=k_seqinfo)\n\n    @classmethod\n    def from_tensor_list(\n        cls,\n        tensors: Sequence[torch.Tensor],\n    ) -> Tuple[\"BlockDiagonalMask\", torch.Tensor]:\n        \"\"\"Creates a :attr:`BlockDiagonalMask` from a list of tensors, and returns the tensors\n        concatenated on the sequence length dimension\n\n        .. figure:: /_static/block_diag_cat_split.png\n\n            See also :attr:`BlockDiagonalMask.split` to split the returned\n            :attr:`torch.Tensor` back to a list of tensors of varying sequence length\n\n        Args:\n            tensors (Sequence[torch.Tensor]): A list of tensors of shape ``[B, M_i, *]``.\n                All tensors should have the same dimension and the same batch size ``B``, but\n                they can have different sequence length ``M``.\n\n        Returns:\n            Tuple[BlockDiagonalMask, torch.Tensor]: The corresponding bias for the attention\n            along with `tensors` concatenated on the sequence length dimension, with shape ``[1, sum_i{M_i}, *]``\n        \"\"\"\n        batch_sizes = [tensor.shape[0] for tensor in tensors]\n        seqlens = []\n        for x in tensors:\n            for _ in range(x.shape[0]):\n                seqlens.append(x.shape[1])\n        block_diag = cls.from_seqlens(seqlens)\n        block_diag._batch_sizes = batch_sizes\n        tensors_bs1 = tuple(x.reshape([1, -1, *x.shape[2:]]) for x in tensors)\n        concat_tensors = torch.cat(tensors_bs1, dim=1)\n        return block_diag, concat_tensors\n\n    @classmethod\n    def from_tensor_lists_qkv(\n        cls,\n        tensors_q: Sequence[torch.Tensor],\n        tensors_k: Sequence[torch.Tensor],\n        tensors_v: Optional[Sequence[torch.Tensor]] = None,\n    ) -> Tuple[\"BlockDiagonalMask\", torch.Tensor, torch.Tensor, Optional[torch.Tensor]]:\n        assert len(tensors_q) == len(tensors_k)\n        assert tensors_v is None or len(tensors_v) == len(tensors_q)\n        batch_sizes = [tensor.shape[0] for tensor in tensors_q]\n        q_seqlens, kv_seqlens = [], []\n        for i, (q, k) in enumerate(zip(tensors_q, tensors_k)):\n            assert q.shape[0] == k.shape[0]\n            q_seqlens += [q.shape[1]] * q.shape[0]\n            kv_seqlens += [k.shape[1]] * k.shape[0]\n            assert tensors_v is None or tensors_v[i].shape[:2] == k.shape[:2]\n        block_diag = cls.from_seqlens(q_seqlens, kv_seqlens)\n        block_diag._batch_sizes = batch_sizes\n        return (\n            block_diag,\n            torch.cat([x.reshape([1, -1, *x.shape[2:]]) for x in tensors_q], dim=1),\n            torch.cat([x.reshape([1, -1, *x.shape[2:]]) for x in tensors_k], dim=1),\n            (\n                torch.cat([x.reshape([1, -1, *x.shape[2:]]) for x in tensors_v], dim=1)\n                if tensors_v is not None\n                else None\n            ),\n        )\n\n    def split_queries(self, tensor: torch.Tensor) -> Sequence[torch.Tensor]:\n        return self.q_seqinfo.split(tensor, self._batch_sizes)\n\n    def split_kv(self, tensor: torch.Tensor) -> Sequence[torch.Tensor]:\n        return self.k_seqinfo.split(tensor, self._batch_sizes)\n\n    def split(self, tensor: torch.Tensor) -> Sequence[torch.Tensor]:\n        \"\"\"The inverse operation of :attr:`BlockDiagonalCausalMask.from_tensor_list`\n\n        Args:\n            tensor (torch.Tensor): Tensor of tokens of shape ``[1, sum_i{M_i}, *]``\n\n        Returns:\n            Sequence[torch.Tensor]: A list of tokens with possibly different sequence lengths\n        \"\"\"\n        assert self.q_seqinfo is self.k_seqinfo\n        return self.q_seqinfo.split(tensor, self._batch_sizes)\n\n    def make_causal(self) -> \"BlockDiagonalCausalMask\":\n        \"\"\"Makes each block causal\"\"\"\n        return BlockDiagonalCausalMask(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=self.k_seqinfo,\n            _batch_sizes=self._batch_sizes,\n        )\n\n    def make_causal_from_bottomright(self) -> \"BlockDiagonalCausalFromBottomRightMask\":\n        \"\"\"Makes each block causal with a possible non-causal prefix\"\"\"\n        return BlockDiagonalCausalFromBottomRightMask(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=self.k_seqinfo,\n            _batch_sizes=self._batch_sizes,\n        )\n\n    def make_local_attention(\n        self, window_size: int\n    ) -> \"BlockDiagonalCausalLocalAttentionMask\":\n        \"\"\"Experimental: Makes each block causal with local attention\"\"\"\n        return BlockDiagonalCausalLocalAttentionMask(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=self.k_seqinfo,\n            _batch_sizes=self._batch_sizes,\n            _window_size=window_size,\n        )\n\n    def make_local_attention_from_bottomright(\n        self, window_size: int\n    ) -> \"BlockDiagonalCausalLocalAttentionFromBottomRightMask\":\n        \"\"\"Experimental: Makes each block causal with local attention, start from bottom right\"\"\"\n        return BlockDiagonalCausalLocalAttentionFromBottomRightMask(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=self.k_seqinfo,\n            _batch_sizes=self._batch_sizes,\n            _window_size=window_size,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalMask(BlockDiagonalMask):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalMask`, except that each block is causal.\n\n    Queries and Keys are each divided into the same number of blocks.\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is farther from the initial key in block i than Q\n    is from the initial query in block i.\n    \"\"\"\n\n    def to(self, device) -> \"BlockDiagonalCausalMask\":\n        assert type(self) is BlockDiagonalCausalMask, \"Please implement in subclass\"\n        return BlockDiagonalCausalMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _batch_sizes=self._batch_sizes,\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return LowerTriangularMask().materialize(\n            shape,\n            dtype=dtype,\n            device=device,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalFromBottomRightMask(BlockDiagonalMask):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalMask`, except that each block is causal.\n    This mask allows for a non-causal prefix\n    NOTE: Each block should have `num_keys >= num_queries` otherwise the forward pass is not\n    defined (softmax of vector of `-inf` in the attention)\n\n    Queries and keys are each divided into the same number of blocks.\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which nearer the final key in block i than Q is to the\n    final query in block i.\n    \"\"\"\n\n    def to(self, device) -> \"BlockDiagonalCausalFromBottomRightMask\":\n        assert (\n            type(self) is BlockDiagonalCausalFromBottomRightMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalFromBottomRightMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _batch_sizes=self._batch_sizes,\n        )\n\n    def __post_init__(self) -> None:\n        for i, ((q_start, q_end), (k_start, k_end)) in enumerate(\n            zip(\n                self.q_seqinfo.intervals(),\n                self.k_seqinfo.intervals(),\n            )\n        ):\n            num_queries = q_end - q_start\n            num_keys = k_end - k_start\n            if num_keys < num_queries:\n                raise ValueError(\n                    f\"Block #{i} has num_keys={num_keys} and num_queries={num_queries}.\"\n                    \" Expected `num_keys >= num_queries`\"\n                )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return LowerTriangularFromBottomRightMask().materialize(\n            shape=shape, dtype=dtype, device=device\n        )\n\n\n@dataclass\nclass BlockDiagonalPaddedKeysMask(AttentionBias):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalMask`,\n    except we support padding for k/v\n\n    The keys and values are divided into blocks which are padded out to\n    the same total length.\n    For example, if there is space for 12 keys, for three blocks of\n    max length 4, but we only want to use the first 2, 3 and 2\n    of each block, use `kv_padding=4` and `kv_seqlens=[2, 3, 2]`.\n    The queries are divided into blocks, without padding, of lengths given by\n    q_seqlen.\n\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is not in use (i.e. in the padded area).\n    \"\"\"\n\n    q_seqinfo: _SeqLenInfo\n    k_seqinfo: _PaddedSeqLenInfo\n\n    def to(self, device) -> \"BlockDiagonalPaddedKeysMask\":\n        assert type(self) is BlockDiagonalPaddedKeysMask, \"Please implement in subclass\"\n        return BlockDiagonalPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return torch.zeros([1], device=device, dtype=dtype)\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        if shape[-1] != self.k_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"k shapes wrong\")\n        if shape[-2] != self.q_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"q shapes wrong\")\n        mask = torch.empty(shape[-2:], dtype=dtype, device=device)\n        mask.fill_(-math.inf)\n        for i, ((q_start, q_end), (k_start, k_end)) in enumerate(\n            zip(\n                self.q_seqinfo.intervals(),\n                self.k_seqinfo.intervals(),\n            )\n        ):\n            mask[q_start:q_end, k_start:k_end] = self._create_block_mask(\n                (q_end - q_start, k_end - k_start),\n                dtype=dtype,\n                device=device,\n            )\n        for _ in range(len(shape) - 2):\n            mask = mask.unsqueeze(0)\n        return mask.expand(shape)\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_padding: int,\n        kv_seqlen: Sequence[int],\n        causal_diagonal: Any = None,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"BlockDiagonalPaddedKeysMask\":\n        \"\"\"Creates a :attr:`BlockDiagonalPaddedKeysMask` from a list of tensor\n        lengths for query and key/value.\n\n        Args:\n            q_seqlen (Sequence[int]): List or tensor of sequence lengths for query tensors\n            kv_padding (int): Padding for k/v - also an upperbound on each individual key length\n            kv_seqlen (Sequence[int]): List or tensor of sequence lengths for key/value.\n            causal_diagonal: unused, for BC only\n        Returns:\n            BlockDiagonalPaddedKeysMask\n        \"\"\"\n        device = _get_default_bias_device(device)\n        assert kv_seqlen is None or len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        k_seqinfo = _PaddedSeqLenInfo.from_seqlens_padded(\n            kv_seqlen, kv_padding, device=device\n        )\n        return cls(q_seqinfo=q_seqinfo, k_seqinfo=k_seqinfo)\n\n    def make_paged(\n        self,\n        block_tables: torch.Tensor,\n        page_size: int,\n        paged_type: Type[\"PagedBlockDiagonalPaddedKeysMask\"],\n    ) -> \"PagedBlockDiagonalPaddedKeysMask\":\n        paged_bias = paged_type(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=_PaddedSeqLenInfo(\n                seqstart=self.k_seqinfo.seqstart,\n                seqstart_py=self.k_seqinfo.seqstart_py,\n                seqlen=self.k_seqinfo.seqlen,\n                seqlen_py=self.k_seqinfo.seqlen_py,\n                padding=block_tables.shape[1] * page_size,\n                max_seqlen=self.k_seqinfo.max_seqlen,\n                min_seqlen=self.k_seqinfo.min_seqlen,\n            ),\n            block_tables=block_tables,\n            page_size=page_size,\n        )\n        return paged_bias\n\n    def make_local_attention(\n        self, window_left: int, window_right: int\n    ) -> \"BlockDiagonalLocalAttentionPaddedKeysMask\":\n        return BlockDiagonalLocalAttentionPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=self.k_seqinfo,\n            window_left=window_left,\n            window_right=window_right,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalWithOffsetPaddedKeysMask(BlockDiagonalPaddedKeysMask):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalMask`,\n    except an offset on causality is allowed for each block and we support padding for k/v\n\n    The keys and values are divided into blocks which are padded out to\n    the same total length.\n    For example, if there is space for 12 keys, for three blocks of\n    max length 4, but we only want to use the first 2, 3 and 2\n    of each block, use `kv_padding=4` and `kv_seqlens=[2, 3, 2]`.\n    The queries are divided into blocks, without padding, of lengths given by\n    q_seqlen.\n\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is not in use (i.e. in the padded area),\n    nor one which is nearer to the final key in block i\n    than Q is to the final query in block i.\n    \"\"\"\n\n    causal_diagonal: Any = None  # unused. Exists for BC only.\n\n    def to(self, device) -> \"BlockDiagonalCausalWithOffsetPaddedKeysMask\":\n        assert (\n            type(self) is BlockDiagonalCausalWithOffsetPaddedKeysMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalWithOffsetPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return LowerTriangularFromBottomRightMask().materialize(\n            shape=shape, dtype=dtype, device=device\n        )\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_padding: int,\n        kv_seqlen: Sequence[int],\n        causal_diagonal: Any = None,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"BlockDiagonalCausalWithOffsetPaddedKeysMask\":\n        \"\"\"Creates a :attr:`BlockDiagonalCausalWithOffsetPaddedKeysMask` from a list of tensor\n        lengths for query and key/value.\n\n        Args:\n            q_seqlen (Sequence[int]): List or tensor of sequence lengths for query tensors\n            kv_padding (int): Padding for k/v - also an upperbound on each individual key length\n            kv_seqlen (Sequence[int]): List or tensor of sequence lengths for key/value.\n            causal_diagonal: unused, for BC only\n        Returns:\n            BlockDiagonalCausalWithOffsetPaddedKeysMask\n        \"\"\"\n        assert kv_seqlen is None or len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        device = _get_default_bias_device(device)\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        k_seqinfo = _PaddedSeqLenInfo.from_seqlens_padded(\n            kv_seqlen, kv_padding, device=device\n        )\n        return cls(q_seqinfo=q_seqinfo, k_seqinfo=k_seqinfo)\n\n\n@dataclass\nclass BlockDiagonalLocalAttentionPaddedKeysMask(BlockDiagonalPaddedKeysMask):\n    \"\"\"\n    Like :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalLocalAttentionPaddedKeysMask`,\n    except that this is non-causal.\n\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is not in use (i.e. in the padded area),\n    nor one whose distance to the final key in block i\n    is more than window_left further or window_right nearer\n    than Q is to the final query in block i.\n\n    A query attends to at most window_left + window_right - 1 keys.\n\n    NOTE that if window_right is 0, then this is like a\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask whose window_size is equal to\n    window_left - 1.\n    \"\"\"\n\n    window_left: int\n    window_right: int\n\n    def to(self, device) -> \"BlockDiagonalLocalAttentionPaddedKeysMask\":\n        assert (\n            type(self) is BlockDiagonalLocalAttentionPaddedKeysMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalLocalAttentionPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            window_left=self.window_left,\n            window_right=self.window_right,\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return LocalAttentionFromBottomRightMask(\n            window_left=self.window_left, window_right=self.window_right\n        ).materialize(shape=shape, dtype=dtype, device=device)\n\n    @classmethod\n    def from_seqlens_local(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_padding: int,\n        kv_seqlen: Sequence[int],\n        window_left: int,\n        window_right: int,\n    ) -> \"BlockDiagonalLocalAttentionPaddedKeysMask\":\n        assert kv_seqlen is None or len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen)\n        k_seqinfo = _PaddedSeqLenInfo.from_seqlens_padded(kv_seqlen, kv_padding)\n        return cls(\n            q_seqinfo=q_seqinfo,\n            k_seqinfo=k_seqinfo,\n            window_left=window_left,\n            window_right=window_right,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalLocalAttentionPaddedKeysMask(BlockDiagonalPaddedKeysMask):\n    \"\"\"\n    Like :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalWithOffsetPaddedKeysMask`,\n    except with a window size.\n\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is not in use (i.e. in the padded area),\n    nor one which is nearer to the final key in block i\n    than Q is to the final query in block i, nor one that is at least\n    window_size further from the final key in block i than Q is\n    to the final query in block i.\n    \"\"\"\n\n    _window_size: int\n\n    def to(self, device) -> \"BlockDiagonalCausalLocalAttentionPaddedKeysMask\":\n        assert (\n            type(self) is BlockDiagonalCausalLocalAttentionPaddedKeysMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalLocalAttentionPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _window_size=self._window_size,\n        )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(\n            shape=shape,\n            dtype=dtype,\n            device=device,\n            window_size=self._window_size,\n            from_bottomright=True,\n        )\n\n    @classmethod\n    def from_seqlens_local(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_padding: int,\n        kv_seqlen: Sequence[int],\n        window_size: int,\n    ) -> \"BlockDiagonalCausalLocalAttentionPaddedKeysMask\":\n        assert kv_seqlen is None or len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen)\n        k_seqinfo = _PaddedSeqLenInfo.from_seqlens_padded(kv_seqlen, kv_padding)\n        return cls(q_seqinfo=q_seqinfo, k_seqinfo=k_seqinfo, _window_size=window_size)\n\n\n@dataclass\nclass PagedBlockDiagonalPaddedKeysMask(AttentionBias):\n    \"\"\"\n    Same as BlockDiagonalPaddedKeysMask, but for paged attention.\n    block_tables has shape [batch_size, max_num_pages] and K/V have shape\n    [1, max_num_pages * page_size, num_heads, head_dim]\n    or [1, max_num_pages * page_size, num_groups, num_heads, head_dim]\n    \"\"\"\n\n    q_seqinfo: _SeqLenInfo\n    k_seqinfo: _PaddedSeqLenInfo\n    block_tables: torch.Tensor\n    page_size: int\n\n    _UNPAGED_TYPE: ClassVar[Type[BlockDiagonalPaddedKeysMask]] = (\n        BlockDiagonalPaddedKeysMask\n    )\n\n    def to(self, device: torch.device) -> \"PagedBlockDiagonalPaddedKeysMask\":\n        assert (\n            type(self) is PagedBlockDiagonalPaddedKeysMask\n        ), \"Please implement in subclass\"\n        return PagedBlockDiagonalPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            block_tables=_to_device(self.block_tables, device),\n            page_size=self.page_size,\n        )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        # First create a non-paged mask, then cut individual pages and\n        # copy them to their places in the physical mask, using block tables\n\n        max_row_len = self.block_tables.shape[1] * self.page_size\n        bias_nonpaged = self._UNPAGED_TYPE(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=_PaddedSeqLenInfo.from_seqlens_padded(\n                self.k_seqinfo.seqlen_py, max_row_len\n            ),\n        )\n        mask_nonpaged = bias_nonpaged.materialize(shape, dtype, device)\n\n        n_used_blocks = cast(int, self.block_tables.max().item() + 1)\n        max_physical_len = n_used_blocks * self.page_size\n        mask_paged = torch.empty(\n            mask_nonpaged.shape[:-1] + (max_physical_len,), dtype=dtype, device=device\n        )\n        mask_paged.fill_(-math.inf)\n        for b, (q_start, q_end) in enumerate(self.q_seqinfo.intervals()):\n            for logical_page_idx in range(self.block_tables.shape[1]):\n                physical_page_idx = cast(\n                    int, self.block_tables[b][logical_page_idx].item()\n                )\n                k_logical_start = b * max_row_len + logical_page_idx * self.page_size\n                k_logical_end = k_logical_start + self.page_size\n                k_physical_start = physical_page_idx * self.page_size\n                k_physical_end = k_physical_start + self.page_size\n                mask_paged[..., q_start:q_end, k_physical_start:k_physical_end] = (\n                    mask_nonpaged[..., q_start:q_end, k_logical_start:k_logical_end]\n                )\n        return mask_paged\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_seqlen: Sequence[int],\n        block_tables: torch.Tensor,\n        page_size: int,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"PagedBlockDiagonalPaddedKeysMask\":\n        \"\"\"Creates a :attr:`PagedBlockDiagonalPaddedKeysMask` from a list of tensor\n        lengths for query and key/value.\n\n        Args:\n            q_seqlen (Sequence[int]): List or tensor of sequence lengths for query tensors\n            kv_padding (int): Padding for k/v - also an upperbound on each individual key length\n            kv_seqlen (Sequence[int]): List or tensor of sequence lengths for key/value.\n            causal_diagonal: unused, for BC only\n        Returns:\n            PagedBlockDiagonalPaddedKeysMask\n        \"\"\"\n        assert len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        device = _get_default_bias_device(device)\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        k_seqinfo = _PaddedSeqLenInfo.from_seqlens_padded(\n            kv_seqlen, padding=block_tables.shape[1] * page_size, device=device\n        )\n        return cls(\n            q_seqinfo=q_seqinfo,\n            k_seqinfo=k_seqinfo,\n            block_tables=block_tables,\n            page_size=page_size,\n        )\n\n\n@dataclass\nclass PagedBlockDiagonalCausalWithOffsetPaddedKeysMask(\n    PagedBlockDiagonalPaddedKeysMask\n):\n    \"\"\"\n    Same as BlockDiagonalCausalWithOffsetPaddedKeysMask, but for paged attention.\n    block_tables has shape [batch_size, max_num_pages] and K/V have shape\n    [1, max_num_pages * page_size, num_heads, head_dim]\n    or [1, max_num_pages * page_size, num_groups, num_heads, head_dim]\n    \"\"\"\n\n    _UNPAGED_TYPE = BlockDiagonalCausalWithOffsetPaddedKeysMask\n\n    def to(\n        self, device: torch.device\n    ) -> \"PagedBlockDiagonalCausalWithOffsetPaddedKeysMask\":\n        assert (\n            type(self) is PagedBlockDiagonalCausalWithOffsetPaddedKeysMask\n        ), \"Please implement in subclass\"\n        return PagedBlockDiagonalCausalWithOffsetPaddedKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            block_tables=_to_device(self.block_tables, device),\n            page_size=self.page_size,\n        )\n\n\n@dataclass\nclass BlockDiagonalGappyKeysMask(AttentionBias):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalMask`,\n    except k/v is gappy.\n\n    A query Q in block i only attends to a key which is in block i.\n    \"\"\"\n\n    q_seqinfo: _SeqLenInfo\n    k_seqinfo: _GappySeqInfo\n\n    def to(self, device: torch.device) -> \"BlockDiagonalGappyKeysMask\":\n        assert type(self) is BlockDiagonalGappyKeysMask, \"Please implement in subclass\"\n        return BlockDiagonalGappyKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n        )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        if shape[-1] != self.k_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"k shapes wrong\", (shape, self.k_seqinfo))\n        if shape[-2] != self.q_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"q shapes wrong\", (shape, self.q_seqinfo))\n        mask = torch.empty(shape[-2:], dtype=dtype, device=device)\n        mask.fill_(-math.inf)\n        for (q_start, q_end), (k_start, k_end) in zip(\n            self.q_seqinfo.intervals(),\n            self.k_seqinfo.intervals(),\n        ):\n            mask[q_start:q_end, k_start:k_end] = 0\n        for _ in range(len(shape) - 2):\n            mask = mask.unsqueeze(0)\n        return mask.expand(shape)\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_seqstarts: Sequence[int],\n        kv_seqlen: Sequence[int],\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"BlockDiagonalGappyKeysMask\":\n        \"\"\"Creates a :attr:`BlockDiagonalGappyKeysMask` from a list of tensor\n        lengths for query and key/value.\n        \"\"\"\n        assert len(q_seqlen) == len(kv_seqlen), (\n            q_seqlen,\n            kv_seqlen,\n        )\n        device = _get_default_bias_device(device)\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        k_seqinfo = _GappySeqInfo.from_seqlens_gappy(\n            kv_seqstarts, kv_seqlen, False, device=device\n        )\n        return cls(q_seqinfo=q_seqinfo, k_seqinfo=k_seqinfo)\n\n    def make_paged(\n        self,\n        block_tables: torch.Tensor,\n        page_size: int,\n        notional_padding: int,\n        paged_type: Type[\"PagedBlockDiagonalGappyKeysMask\"],\n    ) -> AttentionBias:\n        \"\"\"\n        Assuming our keys actually live in separate blocks of length\n        notional_padding, convert to a Paged version, avoiding GPU syncs.\n        \"\"\"\n        if notional_padding % page_size:\n            raise ValueError(\n                \"Notional padding should be divisible by the page size,\"\n                f\" but got {notional_padding=}, {page_size=}.\"\n            )\n        max_row_len = block_tables.shape[1] * page_size\n        new_seqstarts_py = [\n            start - i * notional_padding\n            for i, start in enumerate(self.k_seqinfo.seqstart_py[:-1])\n        ]\n        new_seqstarts_py.append(-1)\n        assert all(\n            0 <= i < max_row_len for i in new_seqstarts_py[:-1]\n        ), f\"{max_row_len=} {new_seqstarts_py=}\"\n\n        # Sequence info is duplicated on CPU and GPU,\n        # but we process them independently to avoid GPU sync.\n        batch_size = len(self.k_seqinfo.seqlen_py)\n        notional_starts = notional_padding * torch.arange(\n            batch_size + 1,\n            device=block_tables.device,\n            dtype=torch.int32,\n        )\n        new_seqstarts = self.k_seqinfo.seqstart - notional_starts\n\n        new_seqlens_py = [\n            i + j for i, j in zip(new_seqstarts_py, self.k_seqinfo.seqlen_py)\n        ]\n        new_seqlens = self.k_seqinfo.seqlen + new_seqstarts[:-1]\n\n        k_seqinfo = _GappySeqInfo(\n            seqlen=new_seqlens,\n            seqlen_py=new_seqlens_py,\n            max_seqlen=self.k_seqinfo.max_seqlen,\n            min_seqlen=self.k_seqinfo.min_seqlen,\n            seqstart=new_seqstarts,\n            seqstart_py=new_seqstarts_py,\n        )\n        assert self.k_seqinfo.max_seqlen <= max_row_len\n        paged_bias = paged_type(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=k_seqinfo,\n            block_tables=block_tables,\n            page_size=page_size,\n        )\n        return paged_bias\n\n\n@dataclass\nclass BlockDiagonalCausalWithOffsetGappyKeysMask(BlockDiagonalGappyKeysMask):\n    \"\"\"\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalMask`,\n    except k/v is gappy.\n\n    A query Q in block i cannot attend to a key which is not in block i,\n    nor one which is nearer to the final key in block i\n    than Q is to the final query in block i.\n    \"\"\"\n\n    def to(self, device: torch.device) -> \"BlockDiagonalCausalWithOffsetGappyKeysMask\":\n        assert (\n            type(self) is BlockDiagonalCausalWithOffsetGappyKeysMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalWithOffsetGappyKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n        )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        if shape[-1] != self.k_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"k shapes wrong\")\n        if shape[-2] != self.q_seqinfo.seqstart_py[-1]:\n            raise ValueError(\"q shapes wrong\")\n        mask = torch.empty(shape[-2:], dtype=dtype, device=device)\n        mask.fill_(-math.inf)\n        for i, ((q_start, q_end), (k_start, k_end)) in enumerate(\n            zip(\n                self.q_seqinfo.intervals(),\n                self.k_seqinfo.intervals(),\n            )\n        ):\n            mask[\n                q_start:q_end, k_start:k_end\n            ] = LowerTriangularFromBottomRightMask().materialize(\n                shape=(q_end - q_start, k_end - k_start), dtype=dtype, device=device\n            )\n\n        for _ in range(len(shape) - 2):\n            mask = mask.unsqueeze(0)\n        return mask.expand(shape)\n\n\n@dataclass\nclass PagedBlockDiagonalGappyKeysMask(AttentionBias):\n    \"\"\"\n    Equivalent BlockDiagonalGappyKeysMask, but for paged attention.\n    block_tables has shape [batch_size, max_num_pages] and K/V have shape\n    [1, max_num_pages * page_size, num_heads, head_dim]\n    or [1, max_num_pages * page_size, num_groups, num_heads, head_dim]\n    \"\"\"\n\n    q_seqinfo: _SeqLenInfo\n    k_seqinfo: _GappySeqInfo\n    block_tables: torch.Tensor\n    page_size: int\n\n    _UNPAGED_TYPE: ClassVar[Type[BlockDiagonalGappyKeysMask]] = (\n        BlockDiagonalGappyKeysMask\n    )\n\n    def to(self, device: torch.device) -> \"PagedBlockDiagonalGappyKeysMask\":\n        assert (\n            type(self) is PagedBlockDiagonalGappyKeysMask\n        ), \"Please implement in subclass\"\n        return PagedBlockDiagonalGappyKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            block_tables=_to_device(self.block_tables, device),\n            page_size=self.page_size,\n        )\n\n    def materialize(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        \"\"\"Materialize the attention bias - for debugging & testing\"\"\"\n        # First create a non-paged mask, then cut individual pages and\n        # copy them to their places in the physical mask, using block tables\n\n        max_row_len = self.block_tables.shape[1] * self.page_size\n        new_seqstarts = [\n            start + i * max_row_len\n            for i, start in enumerate(self.k_seqinfo.seqstart_py[:-1])\n        ] + [shape[-1]]\n        new_seqlens = [\n            end - start\n            for start, end in zip(self.k_seqinfo.seqstart_py, self.k_seqinfo.seqlen_py)\n        ]\n        bias_nonpaged = self._UNPAGED_TYPE(\n            q_seqinfo=self.q_seqinfo,\n            k_seqinfo=_GappySeqInfo.from_seqlens_gappy(\n                new_seqstarts,\n                new_seqlens,\n                False,\n                device=torch.device(device),\n            ),\n        )\n        mask_nonpaged = bias_nonpaged.materialize(shape, dtype, device)\n\n        n_used_blocks = cast(int, self.block_tables.max().item() + 1)\n        max_physical_len = n_used_blocks * self.page_size\n        mask_paged = torch.empty(\n            mask_nonpaged.shape[:-1] + (max_physical_len,), dtype=dtype, device=device\n        )\n        mask_paged.fill_(-math.inf)\n        for b, (q_start, q_end) in enumerate(self.q_seqinfo.intervals()):\n            for logical_page_idx in range(self.block_tables.shape[1]):\n                physical_page_idx = cast(\n                    int, self.block_tables[b][logical_page_idx].item()\n                )\n                k_logical_start = b * max_row_len + logical_page_idx * self.page_size\n                k_logical_end = k_logical_start + self.page_size\n                k_physical_start = physical_page_idx * self.page_size\n                k_physical_end = k_physical_start + self.page_size\n                mask_paged[..., q_start:q_end, k_physical_start:k_physical_end] = (\n                    mask_nonpaged[..., q_start:q_end, k_logical_start:k_logical_end]\n                )\n        return mask_paged\n\n    @classmethod\n    def from_seqlens(\n        cls,\n        q_seqlen: Sequence[int],\n        kv_seqstarts: Sequence[int],\n        kv_seqlen: Sequence[int],\n        block_tables: torch.Tensor,\n        page_size: int,\n        *,\n        device: Optional[torch.device] = None,\n    ) -> \"PagedBlockDiagonalGappyKeysMask\":\n        \"\"\"Creates a :attr:`PagedBlockDiagonalGappyKeysMask` from a list of tensor\n        lengths for query and key/value.\n\n        Note that unlike :attr:`BlockDiagonalGappyKeysMask`, kv_seqstarts is\n        addressing in a different space for each batch element. For example\n        if you were doing a BlockDiagonalPaddedKeysMask with two batch\n        elements and padding=100, but wanted to change it so that the first\n        key is ignored, then you would use BlockDiagonalGappyKeysMask with kv_seqstarts\n        [1, 101, 200]. But if you were using PagedBlockDiagonalPaddedKeysMask\n        but wanted to ignore the first key, you would provide this function with\n        kv_seqstarts = [1, 1].\n        \"\"\"\n        assert len(q_seqlen) == len(kv_seqlen) == len(kv_seqstarts), (\n            q_seqlen,\n            kv_seqlen,\n            kv_seqstarts,\n        )\n        device = block_tables.device if device is None else device\n        q_seqinfo = _SeqLenInfo.from_seqlens(q_seqlen, device=device)\n        k_seqinfo = _GappySeqInfo.from_seqlens_gappy(\n            kv_seqstarts, kv_seqlen, True, device=device\n        )\n        return cls(\n            q_seqinfo=q_seqinfo,\n            k_seqinfo=k_seqinfo,\n            block_tables=block_tables,\n            page_size=page_size,\n        )\n\n\n@dataclass\nclass PagedBlockDiagonalCausalWithOffsetGappyKeysMask(PagedBlockDiagonalGappyKeysMask):\n    \"\"\"\n    Same as BlockDiagonalCausalWithOffsetGappyKeysMask, but for paged attention.\n    block_tables has shape [batch_size, max_num_pages] and K/V have shape\n    [1, max_num_pages * page_size, num_heads, head_dim] or\n    [1, max_num_pages * page_size, num_groups, num_heads, head_dim]\n    \"\"\"\n\n    _UNPAGED_TYPE = BlockDiagonalCausalWithOffsetGappyKeysMask\n\n    def to(\n        self, device: torch.device\n    ) -> \"PagedBlockDiagonalCausalWithOffsetGappyKeysMask\":\n        assert (\n            type(self) is PagedBlockDiagonalCausalWithOffsetGappyKeysMask\n        ), \"Please implement in subclass\"\n        return PagedBlockDiagonalCausalWithOffsetGappyKeysMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            block_tables=_to_device(self.block_tables, device),\n            page_size=self.page_size,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalLocalAttentionMask(BlockDiagonalCausalMask):\n    \"\"\"\n    (Experimental feature)\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalMask`.\n    This makes the mask \"local\" and the attention pattern banded.\n\n    The ith query in a block only attends to keys in its block with index\n    greater than i - window_size and less than or equal to i.\n    \"\"\"\n\n    _window_size: int = 0  # forced due to inheritance and default arguments\n\n    def to(self, device) -> \"BlockDiagonalCausalLocalAttentionMask\":\n        assert (\n            type(self) is BlockDiagonalCausalLocalAttentionMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalLocalAttentionMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _batch_sizes=self._batch_sizes,\n            _window_size=self._window_size,\n        )\n\n    def __post_init__(self):\n        if self._window_size <= 0:\n            raise ValueError(\n                f\"Expected `window_size > 0`, but window_size={self._window_size}\"\n            )\n        q_seqlen = [\n            y - x\n            for x, y in zip(\n                self.q_seqinfo.seqstart_py[:-1], self.q_seqinfo.seqstart_py[1:]\n            )\n        ]\n        kv_seqlen = [\n            y - x\n            for x, y in zip(\n                self.k_seqinfo.seqstart_py[:-1], self.k_seqinfo.seqstart_py[1:]\n            )\n        ]\n        for q, k in zip(q_seqlen, kv_seqlen):\n            if q - self._window_size >= k:\n                # Each query only attends to keys no further than window_size back.\n                # When q > k + window_size, there will be a query for which the window doesn't reach any key.\n                raise RuntimeError(\n                    f\"No keys are attended in q_seqlen {q} k_seqlen {k} with sliding window {self._window_size}\"\n                )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(\n            shape,\n            dtype=dtype,\n            device=device,\n            window_size=self._window_size,\n        )\n\n\n@dataclass\nclass BlockDiagonalCausalLocalAttentionFromBottomRightMask(\n    BlockDiagonalCausalFromBottomRightMask\n):\n    \"\"\"\n    (Experimental feature)\n    Same as :attr:`xformers.ops.fmha.attn_bias.BlockDiagonalCausalMask`.\n    This makes the mask \"local\" and the attention pattern banded.\n\n    A query with distance j from the last query in its block only attends to\n    keys in the same block, and only those whose distance to the last key\n    in the block is greater than or equal to j and less than window_size + j.\n    \"\"\"\n\n    _window_size: int = 0  # forced due to inheritance and default arguments\n\n    def to(self, device) -> \"BlockDiagonalCausalLocalAttentionFromBottomRightMask\":\n        assert (\n            type(self) is BlockDiagonalCausalLocalAttentionFromBottomRightMask\n        ), \"Please implement in subclass\"\n        return BlockDiagonalCausalLocalAttentionFromBottomRightMask(\n            q_seqinfo=self.q_seqinfo.to(device),\n            k_seqinfo=self.k_seqinfo.to(device),\n            _batch_sizes=self._batch_sizes,\n            _window_size=self._window_size,\n        )\n\n    def __post_init__(self):\n        super().__post_init__()\n        if self._window_size <= 0:\n            raise ValueError(\n                f\"Expected `window_size > 0`, but window_size={self._window_size}\"\n            )\n\n    def _create_block_mask(\n        self,\n        shape: Tuple[int, ...],\n        dtype: torch.dtype = torch.float32,\n        device: Union[str, torch.device] = \"cpu\",\n    ) -> torch.Tensor:\n        return _materialize_causal_mask(\n            shape,\n            dtype=dtype,\n            device=device,\n            window_size=self._window_size,\n            from_bottomright=True,\n        )\n\n\ntorch._dynamo.allow_in_graph(LowerTriangularMask)\ntorch._dynamo.allow_in_graph(LowerTriangularMaskWithTensorBias)\n\nVARLEN_BIASES = (\n    BlockDiagonalMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalPaddedKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n    PagedBlockDiagonalGappyKeysMask,\n)\n"
  },
  {
    "path": "xformers/ops/fmha/ck.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom dataclasses import replace\nfrom enum import Enum\nfrom typing import Any, Iterable, List, Mapping, Optional, Set, Tuple, Union\n\nimport torch\n\nfrom ..common import get_operator, register_operator\nfrom . import attn_bias\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalCausalFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionMask,\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    BlockDiagonalCausalMask,\n    BlockDiagonalCausalWithOffsetGappyKeysMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalMask,\n    BlockDiagonalPaddedKeysMask,\n    LowerTriangularFromBottomRightLocalAttentionMask,\n    LowerTriangularFromBottomRightMask,\n    LowerTriangularMask,\n    LowerTriangularMaskWithTensorBias,\n    PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n)\nfrom .common import (\n    AttentionBwOpBase,\n    AttentionFwOpBase,\n    check_lastdim_alignment_stride1,\n    Context,\n    Gradients,\n    Inputs,\n)\n\n\ndef _minimum_gemm_alignment(inp: Inputs) -> int:\n    return 1\n\n\ndef _get_seqlen_info(\n    inp: Inputs,\n) -> Tuple[\n    Optional[torch.Tensor], Optional[torch.Tensor], Optional[torch.Tensor], int, int\n]:\n    attn_bias = inp.attn_bias\n    if isinstance(\n        attn_bias,\n        (\n            BlockDiagonalMask,\n            BlockDiagonalPaddedKeysMask,\n            BlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalPaddedKeysMask,\n            PagedBlockDiagonalGappyKeysMask,\n        ),\n    ):\n        attn_bias.k_seqinfo.to(inp.query.device)\n        attn_bias.q_seqinfo.to(inp.query.device)\n        seqstart_k = attn_bias.k_seqinfo.seqstart\n        seqstart_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n        seqlen = (\n            None\n            if isinstance(attn_bias, BlockDiagonalMask)\n            else attn_bias.k_seqinfo.seqlen\n        )\n    else:\n        seqstart_k = None\n        seqstart_q = None\n        max_seqlen_q = -1\n        max_seqlen_k = -1\n        seqlen = None\n\n    if isinstance(attn_bias, PagedBlockDiagonalGappyKeysMask):\n        seqstart_k = attn_bias.k_seqinfo.seqstart[:-1]\n        seqlen = seqlen - seqstart_k  # type: ignore\n\n    return seqstart_k, seqstart_q, seqlen, max_seqlen_q, max_seqlen_k\n\n\ndef _get_tensor_bias(\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]],\n) -> Optional[torch.Tensor]:\n    if isinstance(attn_bias, LowerTriangularMaskWithTensorBias):\n        return attn_bias._bias\n    if isinstance(attn_bias, torch.Tensor):\n        return attn_bias\n    return None\n\n\ndef _check_bias_alignment(\n    reasons: List[str], attn_bias: Optional[Union[torch.Tensor, AttentionBias]]\n) -> None:\n    attn_bias_tensor = _get_tensor_bias(attn_bias)\n    if attn_bias_tensor is not None:\n        alignment = 128 // torch.finfo(attn_bias_tensor.dtype).bits\n        show_padding_hint = False\n        for d in range(attn_bias_tensor.ndim - 1):\n            if attn_bias_tensor.stride(d) % alignment != 0:\n                reasons.append(\n                    f\"attn_bias.stride(-2) % {alignment} != 0 (attn_bias.stride() = {attn_bias_tensor.stride()})\"\n                )\n                show_padding_hint = True\n        if show_padding_hint:\n            reasons.append(\n                \"\"\"\\\nHINT: To use an `attn_bias` with a sequence length that is not a multiple of 8, \\\nyou need to ensure memory is aligned by slicing a bigger tensor. \\\nExample: use `attn_bias = torch.zeros([1, 1, 5, 8])[:,:,:,:5]` instead of `torch.zeros([1, 1, 5, 5])`\"\"\"\n            )\n        # We can have stride=0 sometimes if dimension=1\n        if attn_bias_tensor.stride(-1) > 1:\n            reasons.append(\n                f\"attn_bias.stride(-1) > 1 (attn_bias.stride() = {attn_bias_tensor.stride()}) - \"\n                \"you should call `.contiguous()` on the bias\"\n            )\n\n\nclass _CustomMaskType(int, Enum):\n    \"\"\"\n    (Matches CustomMaskType in C++.)\n    \"\"\"\n\n    NoCustomMask = 0\n    CausalFromTopLeft = 1\n    CausalFromBottomRight = 2\n\n\ndef _custom_mask_type(bias: Optional[Union[torch.Tensor, AttentionBias]]) -> int:\n    if isinstance(\n        bias,\n        (\n            LowerTriangularMask,\n            BlockDiagonalCausalMask,\n            BlockDiagonalCausalLocalAttentionMask,\n        ),\n    ):\n        return int(_CustomMaskType.CausalFromTopLeft)\n    if isinstance(\n        bias,\n        (\n            LowerTriangularFromBottomRightMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n            attn_bias.BlockDiagonalCausalFromBottomRightMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ),\n    ):\n        return int(_CustomMaskType.CausalFromBottomRight)\n    return int(_CustomMaskType.NoCustomMask)\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    \"\"\"xFormers' MHA kernel based on Composable Kernel.\"\"\"\n\n    OPERATOR = get_operator(\"xformers\", \"efficient_attention_forward_ck\")\n    SUPPORTED_DEVICES: Set[str] = {\"cuda\"}\n    SUPPORTED_DTYPES: Set[torch.dtype] = {torch.half, torch.bfloat16}\n    SUPPORTED_MAX_K = 512\n\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        torch.Tensor,\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        LowerTriangularMaskWithTensorBias,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalPaddedKeysMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        PagedBlockDiagonalPaddedKeysMask,\n        PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        PagedBlockDiagonalGappyKeysMask,\n    )\n\n    SUPPORTS_DROPOUT = True\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_DIFFERENT_VALUE_EMBED = True\n    SUPPORTS_PARTIAL = True\n    SUPPORTS_BMGHK = True\n    NAME = \"ckF\"\n\n    ERROR_ATOL: Mapping[torch.dtype, float] = {\n        torch.float: 3e-4,\n        torch.half: 6e-3,\n        torch.bfloat16: 2.8e-2,\n    }\n    ERROR_RTOL: Mapping[torch.dtype, float] = {\n        torch.float: 2e-5,\n        torch.half: 3e-3,\n        torch.bfloat16: 2e-2,\n    }\n\n    _TEST_K: List[int] = [\n        32,  # 64x64 kernel\n        96,\n        128,  # 64x128 kernel\n        256,  # 64x128 with accumulation in gmem\n        512,\n    ]\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        if type(inp.attn_bias) not in FwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n        if inp.query.ndim in [1, 2, 3]:\n            raise NotImplementedError(\"Unsupported number of dimensions\")\n        if inp.query.ndim in [4]:\n            return cls.apply_bmhk(inp, needs_gradient=needs_gradient)\n        assert inp.query.ndim == 5, f\"query has shape {inp.query.shape}\"\n        ctx: Optional[Context] = None\n\n        # when the input is expanded 5-D, the group dimension has zero stride\n        if inp.key.stride()[3] == 0:\n            assert (\n                inp.value.stride()[3] == 0\n            ), \"key and value should be expanded in the same way\"\n            k_shape = inp.key.size()\n            k_stride = inp.key.stride()\n            key = inp.key.as_strided(\n                (k_shape[0], k_shape[1], k_shape[2], k_shape[4]),\n                (k_stride[0], k_stride[1], k_stride[2], k_stride[4]),\n            )\n            v_shape = inp.value.size()\n            v_stride = inp.value.stride()\n            value = inp.value.as_strided(\n                (v_shape[0], v_shape[1], v_shape[2], v_shape[4]),\n                (v_stride[0], v_stride[1], v_stride[2], v_stride[4]),\n            )\n        else:\n            key = inp.key.flatten(2, 3)\n            value = inp.value.flatten(2, 3)\n\n        [_, _, G, Hq, _] = inp.query.shape\n        attn_bias_replace = inp.attn_bias\n        if isinstance(inp.attn_bias, LowerTriangularMaskWithTensorBias):\n            bias_tensor = _get_tensor_bias(inp.attn_bias)\n            if bias_tensor is not None and bias_tensor.ndim == 5:\n                attn_bias_replace = LowerTriangularMaskWithTensorBias(\n                    bias_tensor.flatten(1, 2)\n                )\n        elif isinstance(inp.attn_bias, torch.Tensor) and inp.attn_bias.ndim == 5:\n            attn_bias_replace = inp.attn_bias.flatten(1, 2)\n        inp = replace(\n            inp,\n            query=inp.query.flatten(2, 3),\n            key=key,\n            value=value,\n            attn_bias=attn_bias_replace,\n        )\n        out, ctx = cls.apply_bmhk(inp, needs_gradient=needs_gradient)\n        out = out.unflatten(2, (G, Hq))\n        if ctx is not None:\n            lse = ctx.lse.unflatten(1, (G, Hq))\n            ctx = replace(ctx, lse=lse, out=out)\n        return out, ctx\n\n    @classmethod\n    def apply_bmhk(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        if type(inp.attn_bias) not in FwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n        seqstart_k, seqstart_q, seqlen_k, max_seqlen_q, _ = _get_seqlen_info(inp)\n        out, lse, rng_seed, rng_offset = cls.OPERATOR(\n            query=inp.query,\n            key=inp.key,\n            value=inp.value,\n            attn_bias=_get_tensor_bias(inp.attn_bias),\n            seqstart_q=seqstart_q,\n            seqstart_k=seqstart_k,\n            max_seqlen_q=max_seqlen_q,\n            dropout_p=inp.p,\n            compute_logsumexp=needs_gradient,\n            custom_mask_type=_custom_mask_type(inp.attn_bias),\n            scale=inp.scale,\n            seqlen_k=seqlen_k,\n            window_size=(\n                inp.attn_bias._window_size\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        BlockDiagonalCausalLocalAttentionMask,\n                        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n                        LowerTriangularFromBottomRightLocalAttentionMask,\n                        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n                    ),\n                )\n                else None\n            ),\n            block_tables=(\n                inp.attn_bias.block_tables\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        PagedBlockDiagonalPaddedKeysMask,\n                        PagedBlockDiagonalGappyKeysMask,\n                    ),\n                )\n                else None\n            ),\n            page_size=(\n                inp.attn_bias.page_size\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        PagedBlockDiagonalPaddedKeysMask,\n                        PagedBlockDiagonalGappyKeysMask,\n                    ),\n                )\n                else None\n            ),\n        )\n\n        ctx: Optional[Context] = None\n        if needs_gradient:\n            ctx = Context(\n                out=out,\n                lse=lse,\n                # cutlass forward is only compatible with cutlass backward if\n                # dropout is used (because of the way RNG states are passed and the\n                # way random numbers are generated during backward)\n                op_bw=BwOp if inp.p != 0 else None,\n            )\n            if inp.p != 0:\n                ctx.rng_state = torch.tensor(\n                    [rng_seed, rng_offset], dtype=torch.int64, device=\"cpu\"\n                )\n        return out, ctx\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        matmul_alignment_mn = _minimum_gemm_alignment(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, matmul_alignment_mn)\n        _check_bias_alignment(reasons, d.attn_bias)\n        return reasons\n\n\n@register_operator\nclass BwOp(AttentionBwOpBase):\n    __doc__ = FwOp.__doc__\n\n    OPERATOR = get_operator(\"xformers\", \"efficient_attention_backward_ck\")\n    SUPPORTED_DEVICES = FwOp.SUPPORTED_DEVICES\n    SUPPORTED_DTYPES = FwOp.SUPPORTED_DTYPES\n    SUPPORTED_MAX_K = 256\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        torch.Tensor,\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        # TODO: Fix handling of gradient through the fMHA autograd function\n        # LowerTriangularMaskWithTensorBias,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        attn_bias.BlockDiagonalCausalFromBottomRightMask,\n        attn_bias.BlockDiagonalCausalLocalAttentionMask,\n    )\n    SUPPORTS_ATTN_BIAS_GRAD = True\n    SUPPORTS_DROPOUT = FwOp.SUPPORTS_DROPOUT\n    SUPPORTS_CUSTOM_SCALE = FwOp.SUPPORTS_CUSTOM_SCALE\n    SUPPORTS_DIFFERENT_VALUE_EMBED = FwOp.SUPPORTS_DIFFERENT_VALUE_EMBED\n    SUPPORTS_UNPADDED_LSE = True\n    NAME = \"ckB\"\n\n    _TEST_K: List[int] = [\n        32,  # 64x64 kernel\n        64,\n        96,\n        128,  # 64x128/128x128 kernel\n        256,\n    ]\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(BwOp, cls).not_supported_reasons(d)\n        matmul_alignment_mn = _minimum_gemm_alignment(d)\n\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"key\", d.key, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, matmul_alignment_mn)\n        _check_bias_alignment(reasons, d.attn_bias)\n        attn_bias_tensor = _get_tensor_bias(d.attn_bias)\n\n        # Backprop of gradient through broadcasted bias is not supported\n        if attn_bias_tensor is not None and attn_bias_tensor.requires_grad:\n            # Don't forget that inputs are either in BMK or BMHK!\n            if d.query.ndim == 3 and attn_bias_tensor.ndim == 3:\n                expected_bias_shape = (*d.query.shape[:2], d.key.shape[1])\n            else:\n                # bias is B H Mq Mk\n                expected_bias_shape = (\n                    d.query.shape[0],\n                    d.query.shape[2] if d.query.ndim == 4 else 1,\n                    d.query.shape[1],\n                    d.key.shape[1],\n                )\n            if tuple(attn_bias_tensor.shape) != expected_bias_shape:\n                reasons.append(\n                    \"Broadcasting the `attn_bias` tensor is not supported \"\n                    f\"(shape: {tuple(attn_bias_tensor.shape)}\"\n                    f\"/ expected: {expected_bias_shape})\"\n                )\n\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        if type(inp.attn_bias) not in BwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n\n        seqstart_k, seqstart_q, seqlen_k, max_seqlen_q, max_seqlen_k = _get_seqlen_info(\n            inp\n        )\n        dtype = inp.query.dtype\n\n        rng_seed = rng_offset = 0\n        if inp.p != 0.0:\n            if (\n                ctx.rng_state is None\n                or ctx.rng_state.dtype != torch.int64\n                or ctx.rng_state.device.type != \"cpu\"\n                or ctx.rng_state.shape != (2,)\n            ):\n                raise NotImplementedError(f\"Invalid rng_state: {ctx.rng_state}\")\n            rng_seed, rng_offset = ctx.rng_state.tolist()\n\n        grad_q, grad_k, grad_v, grad_bias = cls.OPERATOR(\n            grad.to(dtype),\n            inp.query,\n            inp.key,\n            inp.value,\n            attn_bias=_get_tensor_bias(inp.attn_bias),\n            seqstart_q=seqstart_q,\n            seqstart_k=seqstart_k,\n            max_seqlen_q=max_seqlen_q,\n            max_seqlen_k=max_seqlen_k,\n            seqlen_k=seqlen_k,\n            logsumexp=ctx.lse,\n            output=ctx.out.to(dtype),\n            dropout_p=inp.p,\n            # if not using dropout, seed and offset are irrelevant but still expected\n            # in function signature so just pass 0\n            # seed and offset could be None if a different FW op other than cutlass\n            # was used.\n            rng_seed=rng_seed,\n            rng_offset=rng_offset,\n            custom_mask_type=_custom_mask_type(inp.attn_bias),\n            scale=inp.scale,\n            window_size=(\n                inp.attn_bias._window_size\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        BlockDiagonalCausalLocalAttentionMask,\n                        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n                        LowerTriangularFromBottomRightLocalAttentionMask,\n                    ),\n                )\n                else None\n            ),\n        )\n\n        # c++/CUDA implementation returns an uninitialized tensor if bias doesn't\n        # require grad\n        if not (\n            isinstance(inp.attn_bias, torch.Tensor) and inp.attn_bias.requires_grad\n        ):\n            grad_bias = None\n\n        return Gradients(dq=grad_q, dk=grad_k, dv=grad_v, db=grad_bias)\n"
  },
  {
    "path": "xformers/ops/fmha/ck_splitk.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, Iterable, List, Optional, Tuple\n\nimport torch\n\nfrom xformers.ops.common import get_operator, register_operator\nfrom xformers.ops.fmha.attn_bias import BlockDiagonalCausalWithOffsetPaddedKeysMask\nfrom xformers.ops.fmha.common import (\n    AttentionFwOpBase,\n    check_lastdim_alignment_stride1,\n    Context,\n    Inputs,\n)\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n\n    OPERATOR = get_operator(\"xformers\", \"efficient_attention_forward_decoder_splitk_ck\")\n    SUPPORTED_DEVICES = {\"cuda\"}\n    SUPPORTED_DTYPES = {\n        torch.half,\n        torch.bfloat16,\n        torch.float,\n    }  # Those are dtypes of Q. In the quantized case K/V has dtype int32\n    SUPPORTED_MAX_K = 256\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    )\n    SUPPORTS_DROPOUT = False\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_BMGHK = True\n    NAME = \"ck_splitKF\"\n\n    SPLIT_K: Optional[int] = None\n    BLOCK_M = 16\n    BLOCK_N = 64\n\n    NUM_GROUPS = 1  # Default quantization is row-wise\n\n    @classmethod\n    def shape_not_supported_reasons(\n        cls, Mq: int, Mkv: int, K: int, Kv: int\n    ) -> List[str]:\n        reasons = super().shape_not_supported_reasons(Mq, Mkv, K, Kv)\n        # if K not in {16, 32, 64, 128}:\n        #     reasons.append(f\"Embed dim {K} not supported\")\n        return reasons\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        if d.key.dtype != torch.int32:\n            check_lastdim_alignment_stride1(reasons, \"key\", d.key, 8)\n            check_lastdim_alignment_stride1(reasons, \"value\", d.value, 8)\n        if cls.OPERATOR is None:\n            reasons.append(\"triton is not available\")\n        if d.device.type == \"cuda\":\n            # Has only been tested on 8.0 / 9.0.\n            if torch.cuda.get_device_capability(d.device) < (7, 0):\n                reasons.append(\n                    \"requires GPU with sm80 minimum compute capacity, e.g., A100/H100/L4\"\n                )\n\n        q_len = d.query.shape[1]\n        if isinstance(d.attn_bias, BlockDiagonalCausalWithOffsetPaddedKeysMask):\n            seqinfo = d.attn_bias.q_seqinfo\n            if q_len != seqinfo.seqstart_py[-1]:\n                reasons.append(\n                    f\"Expected total {seqinfo.seqstart_py[-1]} queries not {q_len}\"\n                )\n            q_len = seqinfo.min_seqlen\n            if q_len != seqinfo.max_seqlen:\n                reasons.append(\n                    \"Variable query len is not supported in the presence of causal mask.\"\n                )\n\n        if d.key.ndim in [4, 5] and d.key.shape[-2] != 1:\n            if d.key.stride(-2) == 0 and d.value.stride(-2) == 0 and q_len > 1:\n                reasons.append(\"multiquery is only supported with query seqlen=1\")\n\n        if d.attn_bias is not None and q_len > 1:\n            reasons.append(\n                \"query with seqlen > 1 is not supported in the presence of causal mask\"\n            )\n        return reasons\n\n    @classmethod\n    def get_split_k(cls, B: int, H: int, Mk: int) -> int:\n        \"\"\"Heuristic for the number of splits\"\"\"\n        bh = max(B * H, 1)  # NOTE: Handle B*h=0 case\n        split_k = max(Mk, 1024) // bh\n        max_chunk_size = 64 if Mk <= 512 and bh <= 64 else 128\n        while split_k > 0 and Mk / split_k < max_chunk_size:\n            split_k = split_k // 2\n        split_k = min(split_k, 64)\n        split_k = max(split_k, 1)\n        return split_k\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        attn_bias = inp.attn_bias\n        q, k, v = inp.get_qkv_in_bmghk()\n\n        if attn_bias is not None:\n            assert isinstance(attn_bias, BlockDiagonalCausalWithOffsetPaddedKeysMask)\n            attn_bias.k_seqinfo.to(k.device)\n            attn_bias.q_seqinfo.to(q.device)\n            padding = attn_bias.k_seqinfo.padding\n            seq_positions_gpu = attn_bias.k_seqinfo.seqlen\n        else:\n            padding = k.shape[1]\n            seq_positions_gpu = None\n\n        if attn_bias is not None:\n            # key: (1, B * padding, G, 1 if multiquery else Hkv, D)\n            # value: like key\n            # query: (1, B * q_seqlen, G, Hq, D)\n            multiquery = k.stride(3) == 0\n            if multiquery:\n                key = k[0, :, :, :1].unflatten(0, (-1, padding))\n                value = v[0, :, :, :1].unflatten(0, (-1, padding))\n            else:\n                key = k[0].unflatten(0, (-1, padding))\n                value = v[0].unflatten(0, (-1, padding))\n            query = q[0].unflatten(0, (key.shape[0], -1))\n        else:\n            # key: (B, padding, G, 1 if multiquery else Hkv, D)\n            # value: like key\n            # query: (B, q_seqlen, G, Hq, D)\n            key = k\n            query = q\n            value = v\n\n        B, _, _, H, _ = query.shape\n        _, Mk, _, _, _ = key.shape\n\n        if cls.SPLIT_K is not None:\n            split_k = cls.SPLIT_K\n        else:\n            # Use heuristics\n            split_k = cls.get_split_k(B, H, Mk)\n\n        if inp.scale is not None:\n            qk_scale = inp.scale\n        else:\n            qk_scale = torch.rsqrt(\n                torch.tensor(k.shape[-1], dtype=torch.float32)\n            ).item()\n\n        out = cls.OPERATOR(\n            query=query,\n            key=key,\n            value=value,\n            seq_positions=seq_positions_gpu,\n            scale=qk_scale,\n            split_k=split_k,\n        )\n\n        return out, None\n\n\nclass FwOp_S1(FwOp):\n    SPLIT_K = 1\n    NAME = \"ck_splitK1\"\n\n\nclass FwOp_S2(FwOp):\n    SPLIT_K = 2\n    NAME = \"ck_splitK2\"\n\n\nclass FwOp_S4(FwOp):\n    SPLIT_K = 4\n    NAME = \"ck_splitK4\"\n\n\nclass FwOp_S8(FwOp):\n    SPLIT_K = 8\n    NAME = \"ck_splitK8\"\n\n\nclass FwOp_S16(FwOp):\n    SPLIT_K = 16\n    NAME = \"ck_splitK16\"\n\n\nclass FwOp_S32(FwOp):\n    SPLIT_K = 32\n    NAME = \"ck_splitK32\"\n\n\nclass FwOp_S64(FwOp):\n    SPLIT_K = 64\n    NAME = \"ck_splitK64\"\n\n\nclass FwOp_S128(FwOp):\n    SPLIT_K = 128\n    NAME = \"ck_splitK128\"\n"
  },
  {
    "path": "xformers/ops/fmha/common.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nfrom dataclasses import dataclass\nfrom functools import partial\nfrom typing import (\n    Any,\n    Callable,\n    Iterable,\n    List,\n    Mapping,\n    Optional,\n    Set,\n    Tuple,\n    Type,\n    Union,\n)\n\nimport torch\n\nfrom ..._cpp_lib import _built_with_cuda\nfrom ..common import BaseOperator\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalMask,\n    BlockDiagonalPaddedKeysMask,\n    LowerTriangularMask,\n    LowerTriangularMaskWithTensorBias,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n)\n\n\ndef _is_bias_type_supported_in_BMK(attn_bias_type: Any) -> bool:\n    # NoneType\n    if isinstance(None, attn_bias_type):\n        return True\n    if attn_bias_type in [LowerTriangularMask, torch.Tensor]:\n        return True\n    return False\n\n\ndef _attn_bias_apply(\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]],\n    op: Callable[[torch.Tensor], torch.Tensor],\n) -> Optional[Union[torch.Tensor, AttentionBias]]:\n    if isinstance(attn_bias, torch.Tensor):\n        return op(attn_bias)\n    if isinstance(attn_bias, LowerTriangularMaskWithTensorBias):\n        return LowerTriangularMaskWithTensorBias(op(attn_bias._bias))\n    return attn_bias\n\n\nclass ScaledTensor(torch.Tensor):\n    __slots__ = [\"scale\", \"dequant_func\", \"original_dtype\"]\n\n    # Disabling custom torch function handling for this class\n    __torch_function__ = torch._C._disabled_torch_function_impl\n\n    def __new__(\n        cls,\n        data: torch.Tensor,\n        scale: torch.Tensor,\n        dequant_func: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],\n        original_dtype: torch.dtype,\n        require_grad: bool = False,\n    ) -> \"ScaledTensor\":\n        \"\"\"\n        Creates a new ScaledTensor subclass instance.\n\n        Parameters:\n        - data: The underlying quantized tensor (e.g., int8, int4).\n        - scale: The scale tensor or scalar to be used for dequantization.\n        - dequant_func: A callable that applies dequantization, which takes both the data and scale as input.\n        - original_dtype: The data type before quantization (e.g., float32, float16).\n        - require_grad: Whether or not to track gradients (default: False for inference use).\n        \"\"\"\n        # Use _make_subclass to create a new ScaledTensor instance, which is a subclass of torch.Tensor.\n        instance = torch.Tensor._make_subclass(cls, data, require_grad=require_grad)\n\n        # Store the dequantization scale and function as attributes.\n        instance.scale = scale  # type: ignore\n        instance.dequant_func = dequant_func  # type: ignore\n\n        # Store the original data type of the tensor, so we can cast it back after dequantization.\n        instance.original_dtype = original_dtype  # type: ignore\n\n        # Return the new instance of ScaledTensor.\n        return instance\n\n    def dequantize(self) -> torch.Tensor:\n        \"\"\"\n        Applies the custom dequantization function provided at the tensor's creation.\n        After dequantization, the data is cast back to its original data type.\n        \"\"\"\n        # Explicitly create a new torch.Tensor to ensure the return type is torch.Tensor, not ScaledTensor.\n        data = torch.Tensor(self.float())\n\n        # Call the dequantization function, passing in the data and the scale.\n        dequantized_data = self.dequant_func(data, self.scale)  # type: ignore\n\n        # Cast the dequantized data back to the original data type.\n        return dequantized_data.to(self.original_dtype)  # type: ignore\n\n    def unpack(self) -> Tuple[torch.Tensor, torch.Tensor]:\n        \"\"\"\n        Unpacks the ScaledTensor by returning its data and scale as a tuple.\n        Returns:\n        - A tuple of (data, scale), both of which are torch.Tensor objects.\n        \"\"\"\n        return self.data, self.scale  # type: ignore\n\n    def __repr__(self):\n        \"\"\"\n        Custom string representation for ScaledTensor.\n        \"\"\"\n        return f\"ScaledTensor(data={self.data}, scale={self.scale}, original_dtype={self.original_dtype})\"\n\n\ndef pack_fp8_tensorwise_per_head(\n    x: torch.Tensor, scale: Union[torch.Tensor, float], original_dtype\n) -> ScaledTensor:\n    \"\"\"\n    Pack a tensor into a tensorwise fp8 ScaledTensor.\n    \"\"\"\n    if isinstance(scale, float):\n        scale = torch.tensor([scale], device=x.device)\n\n    def dequant_func(x, scale):\n        return x * scale[:, None, :, None]\n\n    return ScaledTensor(\n        data=x,\n        scale=scale,\n        dequant_func=dequant_func,\n        original_dtype=original_dtype,\n    )\n\n\n@dataclass\nclass Inputs:\n    \"\"\"\n    Stores inputs to the `memory_efficient_attention` operators\n    \"\"\"\n\n    query: torch.Tensor\n    key: torch.Tensor\n    value: torch.Tensor\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]] = None\n    p: float = 0.0\n    scale: Optional[float] = None\n    output_dtype: Optional[torch.dtype] = None\n    is_partial: bool = False\n\n    @property\n    def device(self) -> torch.device:\n        return self.query.device\n\n    @property\n    def scale_float(self) -> float:\n        return self.query.shape[-1] ** (-0.5) if self.scale is None else self.scale\n\n    def get_qkv_in_bmghk(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        if self.query.ndim == 5:\n            return self.query, self.key, self.value\n        if self.query.ndim == 4:\n            return (\n                self.query.unsqueeze(2),\n                self.key.unsqueeze(2),\n                self.value.unsqueeze(2),\n            )\n        if self.value.ndim == 3:\n            return (\n                self.query[:, :, None, None],\n                self.key[:, :, None, None],\n                self.value[:, :, None, None],\n            )\n        assert False\n\n    def normalize_bmhk(self) -> Tuple[int, ...]:\n        if self.query.ndim not in [3, 4, 5]:\n            raise ValueError(\n                f\"Invalid shape for query: {self.query.shape}. \"\n                \"Expected shape [batch, seqlen, head_groups, num_heads_per_group, K]\"\n                \", [batch, seqlen, num_heads, K], or [batch, seqlen, K].\"\n            )\n        if self.value.dtype == torch.int32:\n            # Quantized K/V case, in which the last dims of Q and K are different.\n            # NB we currently don't have any implementations for quantized KV with\n            # SUPPORTS_DIFFERENT_VALUE_EMBED.\n            output_shape = tuple(self.query.shape)\n        else:\n            output_shape = (self.query.shape[:-1]) + (self.value.shape[-1],)\n        # Convert from legacy format\n        if self.query.ndim == 3:\n            self.query = self.query.unsqueeze(2)\n            self.key = self.key.unsqueeze(2)\n            self.value = self.value.unsqueeze(2)\n            self.attn_bias = _attn_bias_apply(\n                self.attn_bias, partial(torch.unsqueeze, dim=1)\n            )\n        return output_shape\n\n    def validate_inputs(self) -> None:\n        qkv = (self.query, self.key, self.value)\n        if self.query.ndim not in (3, 4, 5) or any(\n            x.ndim != self.query.ndim for x in qkv\n        ):\n            raise ValueError(\n                f\"Query/Key/Value should all have BMGHK, BMHK or BMK shape.\\n\"\n                f\"  query.shape: {self.query.shape}\\n\"\n                f\"  key.shape  : {self.key.shape}\\n\"\n                f\"  value.shape: {self.value.shape}\"\n            )\n        if any(x.device != self.query.device for x in qkv):\n            raise ValueError(\"Query/Key/Value should all be on the same device\")\n        if isinstance(\n            self.attn_bias,\n            (\n                BlockDiagonalMask,\n                BlockDiagonalPaddedKeysMask,\n                PagedBlockDiagonalPaddedKeysMask,\n                BlockDiagonalGappyKeysMask,\n                PagedBlockDiagonalGappyKeysMask,\n            ),\n        ):\n            bias_device = self.attn_bias.q_seqinfo.seqstart.device\n            if bias_device != self.query.device:\n                raise ValueError(\n                    f\"Attention bias and Query/Key/Value should be on the same device\\n\"\n                    f\"  query.device: {self.query.device}\\n\"\n                    f\"  attn_bias   : {bias_device}\\n\"\n                )\n\n        quantized_dtypes = self.key.dtype == self.value.dtype == torch.int32\n        non_quantized_dtypes = all(x.dtype == self.query.dtype for x in qkv)\n        if not (quantized_dtypes or non_quantized_dtypes):\n            raise ValueError(\n                \"Query/Key/Value should either all have the same dtype, or \"\n                \"(in the quantized case) Key/Value should have dtype torch.int32\\n\"\n                f\"  query.dtype: {self.query.dtype}\\n\"\n                f\"  key.dtype  : {self.key.dtype}\\n\"\n                f\"  value.dtype: {self.value.dtype}\"\n            )\n        # Biases with tensors attached are meant to be in BMHK format\n        # This would require to permute biases/gradients which can be expensive,\n        # so let's just forbid it - BMK is a legacy format anyway\n        if self.query.ndim == 3 and not _is_bias_type_supported_in_BMK(\n            type(self.attn_bias)\n        ):\n            raise ValueError(\n                f\"Please provide inputs in BMHK format rather \"\n                f\"than BMK when using bias type `{type(self.attn_bias).__name__}`\"\n            )\n        attn_bias_t: Optional[torch.Tensor] = None\n        if isinstance(self.attn_bias, LowerTriangularMaskWithTensorBias):\n            attn_bias_t = self.attn_bias._bias\n        elif isinstance(self.attn_bias, torch.Tensor):\n            attn_bias_t = self.attn_bias\n        if self.query.ndim == 4 and attn_bias_t is not None:\n            expected_shape = (\n                self.query.shape[0],\n                self.query.shape[2],\n                self.query.shape[1],\n                self.key.shape[1],\n            )\n            if attn_bias_t.shape != expected_shape:\n                raise ValueError(\n                    f\"Invalid shape for attention bias: {attn_bias_t.shape} (expected {expected_shape})\\n\"\n                    f\"  query.shape: {self.query.shape}\\n\"\n                    f\"  key.shape  : {self.key.shape}\\n\"\n                    f\"  value.shape: {self.value.shape}\"\n                )\n        if isinstance(self.attn_bias, BlockDiagonalMask):\n            if any(x.shape[0] != 1 for x in qkv):\n                raise ValueError(\n                    f\"Expected batch_size=1 when using block-diagonal bias\\n\"\n                    f\"  query.shape: {self.query.shape}\\n\"\n                    f\"  key.shape  : {self.key.shape}\\n\"\n                    f\"  value.shape: {self.value.shape}\"\n                )\n        if self.p < 0.0 or self.p > 1.0:\n            raise ValueError(f\"Invalid dropout probability: p={self.p}\")\n        # Check that shapes match between inputs\n        B, Mq = self.query.shape[:2]\n        K = self.query.shape[-1]\n        B, Mkv = self.key.shape[:2]\n        Kv = self.value.shape[-1]\n        quantized_kv_cache = self.value.dtype == torch.int32\n        key_embed_dim = Kv if quantized_kv_cache else K\n\n        valid_shapes = True\n        if self.query.ndim == 3:  # BMK\n            valid_shapes = (\n                self.query.shape == (B, Mq, K)\n                and self.key.shape == (B, Mkv, K)\n                and self.value.shape == (B, Mkv, Kv)\n            )\n        H = self.query.shape[-2]\n        if self.query.ndim == 4:  # BMHK\n            valid_shapes = (\n                self.query.shape == (B, Mq, H, K)\n                and self.key.shape == (B, Mkv, H, key_embed_dim)\n                and self.value.shape == (B, Mkv, H, Kv)\n            )\n        G = self.query.shape[2]\n        if self.query.ndim == 5:  # BMNHK\n            valid_shapes = (\n                self.query.shape == (B, Mq, G, H, K)\n                and self.key.shape == (B, Mkv, G, H, key_embed_dim)\n                and self.value.shape == (B, Mkv, G, H, Kv)\n            )\n        if not valid_shapes:\n            raise ValueError(\n                f\"Incompatible shapes for attention inputs:\\n\"\n                f\"  query.shape: {self.query.shape}\\n\"\n                f\"  key.shape  : {self.key.shape}\\n\"\n                f\"  value.shape: {self.value.shape}\\n\"\n                \"HINT: We don't support broadcasting, please use `expand` \"\n                \"yourself before calling `memory_efficient_attention` if you need to\"\n            )\n\n    def get_output_dtype(self) -> torch.dtype:\n        if self.output_dtype is None:\n            if self.is_partial and self.query.dtype is not torch.float64:\n                return torch.float32\n            return self.query.dtype\n        return self.output_dtype\n\n    @property\n    def nbytes(self) -> int:\n        \"\"\"\n        Number of bytes in the input, not counting the attention bias.\n        \"\"\"\n        return sum(\n            x.untyped_storage().nbytes() for x in [self.query, self.key, self.value]\n        )\n\n\n@dataclass\nclass Context:\n    lse: torch.Tensor\n    out: torch.Tensor\n    # NOTE: If `rng_state` is set, `op_bw` should be set as well\n    # as the randomness is backend-dependant\n    op_bw: Optional[Type[\"AttentionBwOpBase\"]] = None\n    rng_state: Optional[Any] = None\n    qkv_share_storage: bool = False\n\n    def get_padded_lse(self, pad_to: int, force_pad_inf: bool = False) -> torch.Tensor:\n        pad_amount = (pad_to - (self.lse.shape[2] % pad_to)) % pad_to\n        lse = self.lse\n        if pad_amount > 0:\n            if force_pad_inf:\n                lse = lse[:, :, : self.out.shape[1]]\n                pad_amount = (pad_to - (lse.shape[2] % pad_to)) % pad_to\n            lse = torch.nn.functional.pad(lse, [0, pad_amount], value=math.inf)\n        elif force_pad_inf and self.out.shape[1] != lse.shape[2]:\n            lse[:, :, self.out.shape[1] :].fill_(math.inf)\n        return lse\n\n\n@dataclass\nclass Gradients:\n    dq: torch.Tensor\n    dk: torch.Tensor\n    dv: torch.Tensor\n    # bias gradient. None if there is no tensor bias or if it doesn't require grad\n    db: Optional[torch.Tensor] = None\n\n\nclass AttentionOpBase(BaseOperator):\n    \"\"\"Base class for any attention operator in xFormers\n\n    See:\n\n    - :attr:`xformers.ops.fmha.cutlass.FwOp`\n    - :attr:`xformers.ops.fmha.cutlass.BwOp`\n    - :attr:`xformers.ops.fmha.flash.FwOp`\n    - :attr:`xformers.ops.fmha.flash.BwOp`\n    - :attr:`xformers.ops.fmha.triton.FwOp`\n    - :attr:`xformers.ops.fmha.triton.BwOp`\n    \"\"\"\n\n    OPERATOR: Any\n    SUPPORTED_DEVICES: Set[str]\n    CUDA_MINIMUM_COMPUTE_CAPABILITY: Tuple[int, int] = (5, 0)\n    CUDA_MAXIMUM_COMPUTE_CAPABILITY: Optional[Tuple[int, int]] = None\n    SUPPORTED_DTYPES: Set[torch.dtype]\n    SUPPORTED_MAX_K: float\n    SUPPORTED_MIN_K: int = 0\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (type(None),)\n    SUPPORTS_DROPOUT: bool\n    SUPPORTS_CUSTOM_SCALE: bool = False\n    SUPPORTS_DIFFERENT_VALUE_EMBED: bool = False\n    SUPPORTS_OUTPUT_DTYPE: bool = False\n    SUPPORTS_PARTIAL: bool = False\n    IS_DETERMINISTIC: bool = True\n    SUPPORTS_BMGHK: bool = False\n    NAME: str\n    OPERATOR_CATEGORY = \"memory_efficient_attention\"\n    # Format for the LSE computed in the FW pass, and accepted in the BW pass,\n    # for BlockDiagonalMask and children.\n    # When using a varlen bias, both the FW and BW operators must have the\n    # same value for `VARLEN_LSE_PACKED`\n    VARLEN_LSE_PACKED: bool = True\n\n    _TEST_BATCH_SIZES: List[int] = [1, 300]\n    _TEST_K: List[int] = [32, 128]\n\n    @classmethod\n    def supports(cls, d: Inputs) -> bool:\n        return not cls.not_supported_reasons(d)\n\n    @classmethod\n    def shape_not_supported_reasons(\n        cls, Mq: int, Mkv: int, K: int, Kv: int\n    ) -> List[str]:\n        reasons = []\n        if not cls.SUPPORTS_DIFFERENT_VALUE_EMBED and K != Kv:\n            reasons.append(\"query.shape[-1] != value.shape[-1]\")\n        if max(K, Kv) > cls.SUPPORTED_MAX_K:\n            reasons.append(\n                f\"max(query.shape[-1], value.shape[-1]) > {cls.SUPPORTED_MAX_K}\"\n            )\n        if min(K, Kv) < cls.SUPPORTED_MIN_K:\n            reasons.append(\n                f\"min(query.shape[-1], value.shape[-1]) < {cls.SUPPORTED_MIN_K}\"\n            )\n        return reasons\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        \"\"\"\n        Returns a list of reasons why this is not supported.\n        The kernel can run these inputs only if the returned list is empty\n        \"\"\"\n        query_shape = d.query.shape\n        reasons = cls.shape_not_supported_reasons(\n            Mq=query_shape[1],\n            Mkv=d.key.shape[1],\n            K=query_shape[-1],\n            Kv=query_shape[-1] if d.value.dtype == torch.int32 else d.value.shape[-1],\n        )\n        device_type = d.query.device.type\n        dtype = d.query.dtype\n        if device_type not in cls.SUPPORTED_DEVICES:\n            reasons.append(f\"device={device_type} (supported: {cls.SUPPORTED_DEVICES})\")\n        if (\n            device_type == \"cuda\"\n            and not _built_with_cuda\n            and (torch.version.hip is None)\n        ):\n            reasons.append(\"xFormers wasn't build with CUDA support\")\n        if device_type == \"cuda\" and (torch.version.hip is None):\n            device_capability = torch.cuda.get_device_capability(d.device)\n            if device_capability < cls.CUDA_MINIMUM_COMPUTE_CAPABILITY:\n                reasons.append(\n                    f\"requires device with capability >= {cls.CUDA_MINIMUM_COMPUTE_CAPABILITY} \"\n                    f\"but your GPU has capability {device_capability} (too old)\"\n                )\n            elif (\n                cls.CUDA_MAXIMUM_COMPUTE_CAPABILITY is not None\n                and device_capability > cls.CUDA_MAXIMUM_COMPUTE_CAPABILITY\n            ):\n                reasons.append(\n                    f\"requires device with capability <= {cls.CUDA_MAXIMUM_COMPUTE_CAPABILITY} \"\n                    f\"but your GPU has capability {device_capability} (too new)\"\n                )\n        if dtype not in cls.SUPPORTED_DTYPES:\n            reasons.append(f\"dtype={dtype} (supported: {cls.SUPPORTED_DTYPES})\")\n        if type(d.attn_bias) not in cls.SUPPORTED_ATTN_BIAS_TYPES:\n            reasons.append(f\"attn_bias type is {type(d.attn_bias)}\")\n        if not cls.SUPPORTS_OUTPUT_DTYPE:\n            if d.output_dtype is not None and d.output_dtype is not dtype:\n                reasons.append(\"Custom output dtype not supported\")\n        if d.is_partial and not cls.SUPPORTS_PARTIAL:\n            reasons.append(\"Partial attention not supported\")\n        if (d.p != 0.0) and not cls.SUPPORTS_DROPOUT:\n            reasons.append(\"dropout > 0.0\")\n        if d.scale is not None and not cls.SUPPORTS_CUSTOM_SCALE:\n            reasons.append(\"has custom scale\")\n        # bfloat16 is only supported on A100+\n        # ... although the kernels can still run and give the\n        # correct result\n        if dtype is torch.bfloat16 and (\n            not device_type.startswith(\"cuda\")\n            or torch.cuda.get_device_capability(d.query.device)[0] < 8\n        ):\n            reasons.append(\"bf16 is only supported on A100+ GPUs\")\n        if not cls.is_available():\n            reasons.append(\n                \"operator wasn't built - see `python -m xformers.info` for more info\"\n            )\n        if not cls.IS_DETERMINISTIC and torch.are_deterministic_algorithms_enabled():\n            reasons.append(\n                \"operator is non-deterministic, but `torch.use_deterministic_algorithms` is set\"\n            )\n        if not cls.SUPPORTS_BMGHK and d.query.ndim == 5:\n            reasons.append(\"operator does not support BMGHK format\")\n        return reasons\n\n\nclass AttentionFwOpBase(AttentionOpBase):\n    ERROR_ATOL: Mapping[torch.dtype, float] = {\n        torch.float: 3e-4,\n        torch.half: 4e-3,\n        torch.bfloat16: 2e-2,\n    }\n    ERROR_RTOL: Mapping[torch.dtype, float] = {\n        torch.float: 2e-5,\n        torch.half: 4e-4,\n        torch.bfloat16: 5e-3,\n    }\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        raise NotImplementedError()\n\n\nclass AttentionBwOpBase(AttentionOpBase):\n    # NOTE on tolerances: These are tested for `scales => (1/32)**0.5`\n    # In the BW pass, imprecisions accumulate in the Q@K.T recalculation\n    # These imprecisions are multiplied by the `scale` and then exponentiated\n    # So if the scale is too high, we get a lot of errors\n\n    ERROR_ATOL: Mapping[torch.dtype, float] = {\n        torch.float: 9e-4,\n        torch.half: 0.2,\n        torch.bfloat16: 0.9,\n    }\n    ERROR_RTOL: Mapping[torch.dtype, float] = {\n        torch.float: 1e-4,\n        torch.half: 2e-2,\n        torch.bfloat16: 0.1,\n    }\n    SUPPORTS_ATTN_BIAS_GRAD = False\n    SUPPORTS_PARTIAL = True\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(AttentionBwOpBase, cls).not_supported_reasons(d)\n        if (\n            isinstance(d.attn_bias, torch.Tensor)\n            and d.attn_bias.requires_grad\n            and not cls.SUPPORTS_ATTN_BIAS_GRAD\n        ):\n            reasons.append(\n                \"Computing the bias gradient is not supported (attn_bias.requires_grad = True)\"\n            )\n\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        raise NotImplementedError()\n\n\nAttentionOp = Tuple[\n    Optional[Type[AttentionFwOpBase]], Optional[Type[AttentionBwOpBase]]\n]\n\n\ndef bmk2bmhk(tensor, num_heads: int) -> torch.Tensor:\n    if tensor.ndim == 4:\n        return tensor\n    return tensor.reshape(\n        [tensor.shape[0] // num_heads, num_heads, tensor.shape[1], tensor.shape[2]]\n    ).permute((0, 2, 1, 3))\n\n\ndef check_lastdim_alignment_stride1(\n    reasons: List[str], name: str, x: torch.Tensor, alignment: int\n) -> None:\n    if x.shape[-1] % alignment != 0:\n        reasons.append(f\"{name}.shape[-1] % {alignment} != 0\")\n    elif x.stride(-2) % alignment != 0:\n        reasons.append(\n            f\"{name}.stride(-2) % {alignment} != 0 ({name}.stride() = {x.stride()})\"\n        )\n    # We can have stride=0 sometimes if dimension=1\n    if x.stride(-1) > 1:\n        reasons.append(\n            f\"{name}.stride(-1) > 1 ({name}.stride() = {x.stride()}) - you should call `.contiguous()` on the input\"\n        )\n"
  },
  {
    "path": "xformers/ops/fmha/cutlass.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom dataclasses import replace\nfrom enum import Enum\nfrom functools import partial\nfrom typing import Any, Iterable, List, Optional, Set, Tuple, Union\n\nimport torch\n\nfrom ..common import get_operator, register_operator\nfrom . import attn_bias\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionMask,\n    BlockDiagonalCausalMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalMask,\n    LowerTriangularFromBottomRightLocalAttentionMask,\n    LowerTriangularFromBottomRightMask,\n    LowerTriangularMask,\n    LowerTriangularMaskWithTensorBias,\n)\nfrom .common import (\n    _attn_bias_apply,\n    AttentionBwOpBase,\n    AttentionFwOpBase,\n    check_lastdim_alignment_stride1,\n    Context,\n    Gradients,\n    Inputs,\n)\nfrom .torch_attention_compat import is_pt_cutlass_compatible\n\n\ndef _uses_tensorcores(sm: int, is_half: bool) -> bool:\n    if sm >= 80:\n        return True\n    if sm >= 70:\n        return is_half\n    return False\n\n\ndef _minimum_gemm_alignment(inp: Inputs) -> int:\n    if inp.device.type != \"cuda\":\n        return 1\n    cap = torch.cuda.get_device_capability(inp.device)\n    sm = cap[0] * 10 + cap[1]\n    bits_per_scalar = {torch.float: 32, torch.half: 16, torch.bfloat16: 16}[\n        inp.query.dtype\n    ]\n    uses_tensorcores = _uses_tensorcores(sm, bits_per_scalar == 16)\n    matmul_alignment_mn = 1\n    if sm >= 80:\n        matmul_alignment_mn = 4\n    if uses_tensorcores:\n        matmul_alignment_mn = max(matmul_alignment_mn, 128 // bits_per_scalar)\n    return matmul_alignment_mn\n\n\ndef _get_seqlen_info(\n    inp: Inputs,\n) -> Tuple[Optional[torch.Tensor], Optional[torch.Tensor], int, int]:\n    attn_bias = inp.attn_bias\n    if isinstance(\n        attn_bias, (BlockDiagonalMask, BlockDiagonalCausalWithOffsetPaddedKeysMask)\n    ):\n        assert attn_bias.k_seqinfo.seqstart.device == inp.query.device\n        seqstart_k = attn_bias.k_seqinfo.seqstart\n        seqstart_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n    else:\n        seqstart_k = None\n        seqstart_q = None\n        max_seqlen_q = -1\n        max_seqlen_k = -1\n\n    return seqstart_k, seqstart_q, max_seqlen_q, max_seqlen_k\n\n\ndef _get_tensor_bias(\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]],\n) -> Optional[torch.Tensor]:\n    if isinstance(attn_bias, LowerTriangularMaskWithTensorBias):\n        return attn_bias._bias\n    if isinstance(attn_bias, torch.Tensor):\n        return attn_bias\n    return None\n\n\ndef _check_bias_alignment(\n    reasons: List[str], attn_bias: Optional[Union[torch.Tensor, AttentionBias]]\n) -> None:\n    attn_bias_tensor = _get_tensor_bias(attn_bias)\n    if attn_bias_tensor is not None:\n        alignment = 128 // torch.finfo(attn_bias_tensor.dtype).bits\n        show_padding_hint = False\n        for d in range(attn_bias_tensor.ndim - 1):\n            if attn_bias_tensor.stride(d) % alignment != 0:\n                reasons.append(\n                    f\"attn_bias.stride(-2) % {alignment} != 0 (attn_bias.stride() = {attn_bias_tensor.stride()})\"\n                )\n                show_padding_hint = True\n        if show_padding_hint:\n            reasons.append(\n                \"\"\"\\\nHINT: To use an `attn_bias` with a sequence length that is not a multiple of 8, \\\nyou need to ensure memory is aligned by slicing a bigger tensor. \\\nExample: use `attn_bias = torch.zeros([1, 1, 5, 8])[:,:,:,:5]` instead of `torch.zeros([1, 1, 5, 5])`\"\"\"\n            )\n        # We can have stride=0 sometimes if dimension=1\n        if attn_bias_tensor.stride(-1) > 1:\n            reasons.append(\n                f\"attn_bias.stride(-1) > 1 (attn_bias.stride() = {attn_bias_tensor.stride()}) - \"\n                \"you should call `.contiguous()` on the bias\"\n            )\n\n\nclass _CustomMaskType(int, Enum):\n    \"\"\"\n    (Matches CustomMaskType in C++.)\n    \"\"\"\n\n    NoCustomMask = 0\n    CausalFromTopLeft = 1\n    CausalFromBottomRight = 2\n\n\ndef _custom_mask_type(bias: Optional[Union[torch.Tensor, AttentionBias]]) -> int:\n    if isinstance(\n        bias,\n        (\n            LowerTriangularMask,\n            BlockDiagonalCausalMask,\n            BlockDiagonalCausalLocalAttentionMask,\n        ),\n    ):\n        return int(_CustomMaskType.CausalFromTopLeft)\n    if isinstance(\n        bias,\n        (\n            LowerTriangularFromBottomRightMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n            attn_bias.BlockDiagonalCausalFromBottomRightMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        ),\n    ):\n        return int(_CustomMaskType.CausalFromBottomRight)\n    return int(_CustomMaskType.NoCustomMask)\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    \"\"\"xFormers' MHA kernel based on CUTLASS.\n    Supports a large number of settings (including without TensorCores, f32 ...)\n    and GPUs as old as P100 (Sm60)\n    \"\"\"\n\n    OPERATOR = (\n        get_operator(\"aten\", \"_efficient_attention_forward\")\n        if is_pt_cutlass_compatible()\n        else None\n    )\n    CUDA_MAXIMUM_COMPUTE_CAPABILITY = (9, 0)\n    SUPPORTED_DEVICES: Set[str] = {\"cuda\"}\n    SUPPORTED_DTYPES: Set[torch.dtype] = {torch.float, torch.half, torch.bfloat16}\n    SUPPORTED_MAX_K = 65536\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        torch.Tensor,\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        LowerTriangularMaskWithTensorBias,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        attn_bias.BlockDiagonalCausalFromBottomRightMask,\n        attn_bias.BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    )\n    SUPPORTS_DROPOUT = True\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_DIFFERENT_VALUE_EMBED = True\n    SUPPORTS_BMGHK = True\n    VARLEN_LSE_PACKED = False\n    NAME = \"cutlassF-pt\"\n\n    _TEST_K: List[int] = [\n        32,  # 64x64 kernel\n        128,  # 64x128 kernel\n        256,  # 64x128 with accumulation in gmem\n    ]\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        if type(inp.attn_bias) not in FwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n        if inp.query.ndim in [3, 4]:\n            return cls.apply_bmhk(inp, needs_gradient=needs_gradient)\n        assert inp.query.ndim == 5, f\"query has shape {inp.query.shape}\"\n        ctx: Optional[Context] = None\n        # XXX: Hackfix for BMGHK with H=1\n        # In that case we don't want to run G different streams because it adds\n        # some overhead\n        if inp.query.ndim == 5 and inp.query.shape[3] == 1:\n            slice_op = partial(torch.squeeze, dim=3)\n            inp = replace(\n                inp,\n                query=slice_op(inp.query),\n                key=slice_op(inp.key),\n                value=slice_op(inp.value),\n                attn_bias=_attn_bias_apply(\n                    inp.attn_bias, partial(torch.squeeze, dim=2)\n                ),\n            )\n            out, ctx = cls.apply_bmhk(inp, needs_gradient=needs_gradient)\n            out = out.unsqueeze(3)\n            if ctx is not None:\n                ctx = replace(ctx, lse=ctx.lse.unsqueeze(1), out=out)\n            return out, ctx\n\n        # Workaround until this is properly implemented in C++\n        # run each head group in a different stream\n        n_groups = inp.key.shape[2]\n        main_stream = torch.cuda.current_stream()\n        streams = [main_stream] + [\n            torch.cuda.Stream(device=inp.query.device) for _ in range(n_groups - 1)\n        ]\n        outs = []\n        for group, stream in enumerate(streams):\n            stream.wait_stream(main_stream)\n            with torch.cuda.stream(stream):\n                query = inp.query[:, :, group]\n                key = inp.key[:, :, group]\n                value = inp.value[:, :, group]\n                bias = _attn_bias_apply(\n                    inp.attn_bias, partial(torch.select, dim=1, index=group)\n                )\n                outs.append(\n                    cls.apply_bmhk(\n                        replace(inp, query=query, key=key, value=value, attn_bias=bias),\n                        needs_gradient=needs_gradient,\n                    )\n                )\n        for s in streams[1:]:\n            main_stream.wait_stream(s)\n        out = torch.stack([o[0] for o in outs], dim=2)\n        if needs_gradient:\n            ctx = Context(\n                out=out,\n                lse=torch.stack([o[1].lse for o in outs], dim=1),  # type: ignore\n                op_bw=outs[0][1].op_bw,  # type: ignore\n            )\n        return out, ctx\n\n    @classmethod\n    def apply_bmhk(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        if type(inp.attn_bias) not in FwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n        seqstart_k, seqstart_q, max_seqlen_q, max_seqlen_k = _get_seqlen_info(inp)\n        out, lse, rng_seed, rng_offset, _, _ = cls.OPERATOR(\n            query=inp.query,\n            key=inp.key,\n            value=inp.value,\n            bias=_get_tensor_bias(inp.attn_bias),\n            cu_seqlens_q=seqstart_q,\n            cu_seqlens_k=seqstart_k,\n            max_seqlen_q=max_seqlen_q,\n            max_seqlen_k=max_seqlen_k,\n            dropout_p=inp.p,\n            compute_log_sumexp=needs_gradient,\n            custom_mask_type=_custom_mask_type(inp.attn_bias),\n            scale=inp.scale,\n            seqlen_k=(\n                inp.attn_bias.k_seqinfo.seqlen\n                if isinstance(\n                    inp.attn_bias, BlockDiagonalCausalWithOffsetPaddedKeysMask\n                )\n                else None\n            ),\n            window_size=(\n                inp.attn_bias._window_size\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        BlockDiagonalCausalLocalAttentionMask,\n                        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n                        LowerTriangularFromBottomRightLocalAttentionMask,\n                    ),\n                )\n                else None\n            ),\n        )\n        ctx: Optional[Context] = None\n        if needs_gradient:\n            ctx = Context(out=out, lse=lse)\n            if inp.p != 0:\n                # cutlass forward is only compatible with cutlass backward if\n                # dropout is used (because of the way RNG states are passed and the\n                # way random numbers are generated during backward)\n                ctx.rng_state = (rng_seed, rng_offset)\n                ctx.op_bw = BwOp\n        return out, ctx\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        matmul_alignment_mn = _minimum_gemm_alignment(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, matmul_alignment_mn)\n        _check_bias_alignment(reasons, d.attn_bias)\n        return reasons\n\n\n@register_operator\nclass BwOp(AttentionBwOpBase):\n    __doc__ = FwOp.__doc__\n\n    OPERATOR = (\n        get_operator(\"aten\", \"_efficient_attention_backward\")\n        if is_pt_cutlass_compatible()\n        else None\n    )\n    CUDA_MAXIMUM_COMPUTE_CAPABILITY = FwOp.CUDA_MAXIMUM_COMPUTE_CAPABILITY\n    SUPPORTED_DEVICES = FwOp.SUPPORTED_DEVICES\n    SUPPORTED_DTYPES = FwOp.SUPPORTED_DTYPES\n    SUPPORTED_MAX_K = FwOp.SUPPORTED_MAX_K\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        torch.Tensor,\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        # TODO: Still some infs/nans in the BW pass for\n        # local + causal\n        # LowerTriangularFromBottomRightLocalAttentionMask,\n        # TODO: Fix handling of gradient through the fMHA autograd function\n        # LowerTriangularMaskWithTensorBias,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        attn_bias.BlockDiagonalCausalFromBottomRightMask,\n        attn_bias.BlockDiagonalCausalLocalAttentionMask,\n    )\n    SUPPORTS_ATTN_BIAS_GRAD = True\n    SUPPORTS_DROPOUT = FwOp.SUPPORTS_DROPOUT\n    SUPPORTS_CUSTOM_SCALE = FwOp.SUPPORTS_CUSTOM_SCALE\n    SUPPORTS_DIFFERENT_VALUE_EMBED = FwOp.SUPPORTS_DIFFERENT_VALUE_EMBED\n    VARLEN_LSE_PACKED = False\n    NAME = \"cutlassB-pt\"\n\n    _TEST_K: List[int] = [\n        32,  # 64x64 kernel\n        128,  # 64x128/128x128 kernel\n        256,  # 64x128 with accumulation in gmem\n    ]\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(BwOp, cls).not_supported_reasons(d)\n        matmul_alignment_mn = _minimum_gemm_alignment(d)\n\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"key\", d.key, matmul_alignment_mn)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, matmul_alignment_mn)\n        _check_bias_alignment(reasons, d.attn_bias)\n        attn_bias_tensor = _get_tensor_bias(d.attn_bias)\n\n        # Backprop of gradient through broadcasted bias is not supported\n        if attn_bias_tensor is not None and attn_bias_tensor.requires_grad:\n            # Don't forget that inputs are either in BMK or BMHK!\n            if d.query.ndim == 3 and attn_bias_tensor.ndim == 3:\n                expected_bias_shape = (*d.query.shape[:2], d.key.shape[1])\n            else:\n                # bias is B H Mq Mk\n                expected_bias_shape = (\n                    d.query.shape[0],\n                    d.query.shape[2] if d.query.ndim == 4 else 1,\n                    d.query.shape[1],\n                    d.key.shape[1],\n                )\n            if tuple(attn_bias_tensor.shape) != expected_bias_shape:\n                reasons.append(\n                    \"Broadcasting the `attn_bias` tensor is not supported \"\n                    f\"(shape: {tuple(attn_bias_tensor.shape)}\"\n                    f\"/ expected: {expected_bias_shape})\"\n                )\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        if type(inp.attn_bias) not in BwOp.SUPPORTED_ATTN_BIAS_TYPES:\n            raise NotImplementedError(\"Unsupported attn_bias type\")\n\n        seqstart_k, seqstart_q, max_seqlen_q, max_seqlen_k = _get_seqlen_info(inp)\n        dtype = inp.query.dtype\n\n        rng_seed = rng_offset = torch.Tensor()\n        if inp.p != 0.0:\n            assert ctx.rng_state is not None\n            rng_seed, rng_offset = ctx.rng_state\n        tensor_bias = _get_tensor_bias(inp.attn_bias)\n\n        force_pad_inf = torch.cuda.get_device_capability(inp.query.device) == (7, 5)\n        grad_q, grad_k, grad_v, grad_bias = cls.OPERATOR(\n            grad.to(dtype),\n            inp.query,\n            inp.key,\n            inp.value,\n            bias=tensor_bias,\n            bias_requires_grad=(\n                tensor_bias.requires_grad if tensor_bias is not None else False\n            ),\n            cu_seqlens_q=seqstart_q,\n            cu_seqlens_k=seqstart_k,\n            max_seqlen_q=max_seqlen_q,\n            max_seqlen_k=max_seqlen_k,\n            logsumexp=ctx.get_padded_lse(32, force_pad_inf=force_pad_inf),\n            out=ctx.out.to(dtype),\n            dropout_p=inp.p,\n            # if not using dropout, seed and offset are irrelevant but still expected\n            # in function signature so just pass 0\n            # seed and offset could be None if a different FW op other than cutlass\n            # was used.\n            philox_seed=rng_seed,\n            philox_offset=rng_offset,\n            custom_mask_type=_custom_mask_type(inp.attn_bias),\n            scale=inp.scale,\n            num_splits_key=None,  # Let C++ determine it\n            window_size=(\n                inp.attn_bias._window_size\n                if isinstance(\n                    inp.attn_bias,\n                    (\n                        BlockDiagonalCausalLocalAttentionMask,\n                        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n                        LowerTriangularFromBottomRightLocalAttentionMask,\n                    ),\n                )\n                else None\n            ),\n        )\n\n        # c++/CUDA implementation returns an uninitialized tensor if bias doesn't\n        # require grad\n        if not (\n            isinstance(inp.attn_bias, torch.Tensor) and inp.attn_bias.requires_grad\n        ):\n            grad_bias = None\n\n        return Gradients(dq=grad_q, dk=grad_k, dv=grad_v, db=grad_bias)\n"
  },
  {
    "path": "xformers/ops/fmha/cutlass_blackwell.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Any, Iterable, List, Optional, Set, Tuple, Union\n\nimport torch\n\nfrom ..common import register_operator\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalCausalFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionMask,\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    BlockDiagonalCausalMask,\n    BlockDiagonalCausalWithOffsetGappyKeysMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalLocalAttentionPaddedKeysMask,\n    BlockDiagonalMask,\n    BlockDiagonalPaddedKeysMask,\n    LocalAttentionFromBottomRightMask,\n    LowerTriangularFromBottomRightLocalAttentionMask,\n    LowerTriangularFromBottomRightMask,\n    LowerTriangularMask,\n)\n\nfrom .common import AttentionBwOpBase, AttentionFwOpBase, Context, Gradients, Inputs\n\n\ndef _get_operator(name: str):\n    def no_such_operator(*args, **kwargs):\n        raise RuntimeError(\n            \"No such operator \"\n            f\"fbgemm_gpu.experimental.gen_ai.attention.cutlass_blackwell_fmha.{name} \"\n            \"- did you forget to build xformers with `python setup.py develop`?\"\n        )\n\n    def no_cuda_environment(*args, **kwargs):\n        raise RuntimeError(\n            \"The operator \"\n            f\"fbgemm_gpu.experimental.gen_ai.attention.cutlass_blackwell_fmha.{name} \"\n            \"cannot run in a non-cuda environment.\"\n        )\n\n    try:\n        from fbgemm_gpu.experimental.gen_ai.attention.cutlass_blackwell_fmha import (\n            cutlass_blackwell_fmha_interface as fmha,\n        )\n\n        return getattr(fmha, name)\n    except (RuntimeError, ModuleNotFoundError):\n        return no_such_operator\n    except OSError as e:\n        if torch.cuda.is_available() is False:\n            return no_cuda_environment\n        raise e\n\n\ndef _convert_input_format(\n    inp: Inputs,\n) -> Tuple[\n    Inputs,\n    Optional[torch.Tensor],\n    Optional[int],\n    Optional[torch.Tensor],\n    Optional[int],\n    Optional[torch.Tensor],\n]:\n    assert inp.query.ndim in (4, 5)\n    query, key, value = inp.query, inp.key, inp.value\n\n    attn_bias = inp.attn_bias\n    if isinstance(attn_bias, BlockDiagonalMask):\n        assert attn_bias.k_seqinfo.seqstart.device == inp.query.device\n        cu_seqlen_k = attn_bias.k_seqinfo.seqstart\n        cu_seqlen_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n        seqused_k = None\n    elif isinstance(\n        attn_bias,\n        (\n            BlockDiagonalPaddedKeysMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            BlockDiagonalGappyKeysMask,\n            BlockDiagonalCausalWithOffsetGappyKeysMask,\n            BlockDiagonalLocalAttentionPaddedKeysMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        ),\n    ):\n        assert attn_bias.k_seqinfo.seqstart.device == inp.query.device\n        cu_seqlen_k = attn_bias.k_seqinfo.seqstart\n        cu_seqlen_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n        # All these mask types inherit from classes that have seqlen attribute\n        seqused_k = attn_bias.k_seqinfo.seqlen\n        assert seqused_k is not None\n    else:\n        cu_seqlen_k = None\n        cu_seqlen_q = None\n        seqused_k = None\n        max_seqlen_q = None\n        max_seqlen_k = None\n\n    if query.ndim == 5:  # GQA\n        # Fold the group/head_in_group dimensions together\n        def fold(x):\n            # Either the head is replicated\n            if x.stride(3) == 0:\n                return x[:, :, :, 0]\n\n            # Or we reshape\n            return x.reshape(\n                [\n                    x.shape[0],\n                    x.shape[1],\n                    -1,\n                    x.shape[4],\n                ]\n            )\n\n        query = fold(query)\n        key = fold(key)\n        value = fold(value)\n\n    if cu_seqlen_k is not None and query.ndim == 4:\n        # Fold to 3D when using varlen\n        def fold(x):\n            assert x.shape[0] == 1\n            x = x.squeeze(0)\n            assert x.ndim == 3\n            return x\n\n        query = fold(query)\n        key = fold(key)\n        value = fold(value)\n\n    new_inp = Inputs(\n        query=query,\n        key=key,\n        value=value,\n        attn_bias=attn_bias,\n        p=inp.p,\n        scale=inp.scale,\n        output_dtype=inp.output_dtype,\n        is_partial=inp.is_partial,\n    )\n    return new_inp, cu_seqlen_q, max_seqlen_q, cu_seqlen_k, max_seqlen_k, seqused_k\n\n\ndef _is_seqlen_q_le_seqlen_k(\n    cu_seqlens_q_py: List[int], cu_seqlens_k_py: List[int]\n) -> bool:\n    if len(cu_seqlens_q_py) < 2 or len(cu_seqlens_k_py) < 2:\n        # The seqlens q and k info does not exist on CPU\n        return True\n    cu_seqlens_q = torch.as_tensor(cu_seqlens_q_py, dtype=torch.int, device=\"cpu\")\n    cu_seqlens_k = torch.as_tensor(cu_seqlens_k_py, dtype=torch.int, device=\"cpu\")\n    seqlens_q = cu_seqlens_q[1:] - cu_seqlens_q[:-1]\n    seqlens_k = cu_seqlens_k[1:] - cu_seqlens_k[:-1]\n    return bool(torch.all(seqlens_k >= seqlens_q).item())\n\n\ndef _is_causal(attn_bias: Union[torch.Tensor, AttentionBias, None]) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            LowerTriangularMask,\n            BlockDiagonalCausalMask,\n            LowerTriangularFromBottomRightMask,\n            BlockDiagonalCausalFromBottomRightMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n            BlockDiagonalCausalLocalAttentionMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            BlockDiagonalCausalWithOffsetGappyKeysMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ),\n    )\n\n\ndef _is_bottom_right(attn_bias: Union[torch.Tensor, AttentionBias, None]) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            LowerTriangularFromBottomRightMask,\n            BlockDiagonalCausalFromBottomRightMask,\n            LocalAttentionFromBottomRightMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            BlockDiagonalLocalAttentionPaddedKeysMask,\n            BlockDiagonalCausalWithOffsetGappyKeysMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        ),\n    )\n\n\ndef _window_size(\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]],\n) -> Tuple[int, int]:\n    win_left = -1\n    win_right = -1\n    if isinstance(\n        attn_bias,\n        (\n            BlockDiagonalCausalLocalAttentionMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        ),\n    ):\n        win_left = attn_bias._window_size - 1\n    if isinstance(\n        attn_bias,\n        (\n            BlockDiagonalLocalAttentionPaddedKeysMask,\n            LocalAttentionFromBottomRightMask,\n        ),\n    ):\n        win_left = attn_bias.window_left\n        win_right = attn_bias.window_right\n    return (win_left, win_right)\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    OPERATOR = _get_operator(\"_cutlass_blackwell_fmha_forward\")\n    SUPPORTED_DEVICES: Set[str] = {\"cuda\"}\n    SUPPORTED_DTYPES: Set[torch.dtype] = {torch.bfloat16, torch.float16}\n    SUPPORTED_MAX_K = 128\n    SUPPORTED_MIN_K = 64\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalPaddedKeysMask,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalLocalAttentionPaddedKeysMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        LocalAttentionFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    )\n    SUPPORTS_DROPOUT = False\n    SUPPORTS_CUSTOM_SCALE = False\n    SUPPORTS_DIFFERENT_VALUE_EMBED = False\n    SUPPORTS_BMGHK = True\n    VARLEN_LSE_PACKED = True\n    SUPPORTS_PARTIAL = False\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = (10, 0)\n    NAME = \"cutlassF-blackwell\"\n\n    _TEST_K: List[int] = [64, 128]\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        if isinstance(d.attn_bias, BlockDiagonalCausalMask):\n            (\n                _,\n                cu_seqlens_q,\n                _,\n                cu_seqlens_k,\n                _,\n                _,\n            ) = _convert_input_format(d)\n            if not _is_seqlen_q_le_seqlen_k(\n                d.attn_bias.q_seqinfo.seqstart_py,\n                d.attn_bias.k_seqinfo.seqstart_py,\n            ):\n                reasons.append(\"seqlens_k must be >= seqlens_q\")\n\n        if d.query.ndim < 4 or d.key.ndim < 4 or d.value.ndim < 4:\n            reasons.append(\"Only supports BMHK or BMGHK\")\n\n        return reasons\n\n    @classmethod\n    def shape_not_supported_reasons(\n        cls, Mq: int, Mkv: int, K: int, Kv: int\n    ) -> List[str]:\n        reasons = super().shape_not_supported_reasons(Mq, Mkv, K, Kv)\n        if K not in [64, 128] or Kv not in [64, 128]:\n            reasons.append(f\"Embed dim {K} not supported\")\n        elif Mkv != 0 and Mq > Mkv:\n            reasons.append(f\"Only support Mq ({Mq}) <= Mk ({Mkv})\")\n        return reasons\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        q_shape = inp.query.shape\n        (\n            inp,\n            cu_seqlens_q,\n            max_seq_len_q,\n            cu_seqlens_k,\n            max_seq_len_k,\n            seqused_k,\n        ) = _convert_input_format(inp)\n\n        window_left, window_right = _window_size(inp.attn_bias)\n\n        if inp.query.numel() > 0 and inp.key.numel() > 0:\n            out, lse = cls.OPERATOR(\n                q=inp.query,\n                k=inp.key,\n                v=inp.value,\n                cu_seqlens_q=cu_seqlens_q,\n                cu_seqlens_k=cu_seqlens_k,\n                seqlen_kv=seqused_k,\n                max_seq_len_q=max_seq_len_q,\n                max_seq_len_k=max_seq_len_k,\n                softmax_scale=inp.scale,\n                causal=_is_causal(inp.attn_bias),\n                window_left=window_left,\n                window_right=window_right,\n                bottom_right=_is_bottom_right(inp.attn_bias),\n            )\n        else:\n            out = torch.zeros_like(inp.query)\n            if cu_seqlens_q is None:\n                assert inp.query.ndim == 4\n                B, M, H, K = inp.query.shape\n                lse_shape = [B, H, M]\n            else:\n                assert inp.query.ndim == 3\n                M, H, K = inp.query.shape\n                lse_shape = [1, H, M]\n            lse = torch.zeros(*lse_shape, dtype=torch.float, device=out.device)\n        out = out.reshape(q_shape)\n        if not needs_gradient:\n            return out, None\n        return out, Context(out=out, lse=lse)\n\n\n@register_operator\nclass BwOp(AttentionBwOpBase):\n    __doc__ = FwOp.__doc__\n\n    OPERATOR = _get_operator(\"_cutlass_blackwell_fmha_backward\")\n\n    SUPPORTED_DEVICES = FwOp.SUPPORTED_DEVICES\n    SUPPORTED_DTYPES = FwOp.SUPPORTED_DTYPES\n    SUPPORTED_MAX_K = FwOp.SUPPORTED_MAX_K\n    SUPPORTED_MIN_K = FwOp.SUPPORTED_MIN_K\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        LocalAttentionFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    )\n    SUPPORTS_ATTN_BIAS_GRAD = False\n    SUPPORTS_DROPOUT = FwOp.SUPPORTS_DROPOUT\n    SUPPORTS_CUSTOM_SCALE = FwOp.SUPPORTS_CUSTOM_SCALE\n    SUPPORTS_DIFFERENT_VALUE_EMBED = False\n    SUPPORTS_BMGHK = False\n    VARLEN_LSE_PACKED = True\n    SUPPORTS_PARTIAL = False\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = (10, 0)\n    NAME = \"cutlassB-blackwell\"\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(BwOp, cls).not_supported_reasons(d)\n        if isinstance(d.attn_bias, BlockDiagonalCausalMask):\n            (\n                _,\n                cu_seqlens_q,\n                _,\n                cu_seqlens_k,\n                _,\n                _,\n            ) = _convert_input_format(d)\n            if not _is_seqlen_q_le_seqlen_k(\n                d.attn_bias.q_seqinfo.seqstart_py,\n                d.attn_bias.k_seqinfo.seqstart_py,\n            ):\n                reasons.append(\"seqlens_k must be >= seqlens_q\")\n\n        if d.query.ndim != 4 or d.key.ndim != 4 or d.value.ndim != 4:\n            reasons.append(\"Only supports BMHK format\")\n\n        return reasons\n\n    @classmethod\n    def shape_not_supported_reasons(\n        cls, Mq: int, Mkv: int, K: int, Kv: int\n    ) -> List[str]:\n        reasons = super().shape_not_supported_reasons(Mq, Mkv, K, Kv)\n        if K not in [64, 128]:\n            reasons.append(f\"Embed dim {K} not supported\")\n        elif Mkv != 0 and Mq > Mkv:\n            reasons.append(f\"Only support Mq ({Mq}) <= Mk ({Mkv})\")\n        elif Mq < 8:\n            reasons.append(f\"Only support Mq ({Mq}) >= 8\")\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        assert inp.query.ndim == 4\n        dq_shape, dk_shape, dv_shape = inp.query.shape, inp.key.shape, inp.value.shape\n        (\n            inp,\n            cu_seqlens_q,\n            max_seq_len_q,\n            cu_seqlens_k,\n            max_seq_len_k,\n            _,\n        ) = _convert_input_format(inp)\n\n        window_left, window_right = _window_size(inp.attn_bias)\n\n        is_varlen = cu_seqlens_q is not None\n        if is_varlen:\n\n            def fold(x):\n                assert x.shape[0] == 1\n                x = x.squeeze(0)\n                assert x.ndim == 3\n                return x\n\n            grad = fold(grad)\n            ctx.out = fold(ctx.out)\n\n        if inp.query.numel() and inp.key.numel():\n            grads = Gradients(\n                *cls.OPERATOR(\n                    dout=grad,\n                    q=inp.query,\n                    k=inp.key,\n                    v=inp.value,\n                    out=ctx.out,\n                    softmax_lse=ctx.lse,\n                    cu_seqlens_q=cu_seqlens_q,\n                    cu_seqlens_k=cu_seqlens_k,\n                    max_seq_len_q=max_seq_len_q,\n                    max_seq_len_k=max_seq_len_k,\n                    causal=_is_causal(inp.attn_bias),\n                    window_left=window_left,\n                    window_right=window_right,\n                    bottom_right=_is_bottom_right(inp.attn_bias),\n                )\n            )\n        else:\n            grads = Gradients(\n                dq=torch.zeros_like(inp.query),\n                dk=torch.zeros_like(inp.key),\n                dv=torch.zeros_like(inp.value),\n            )\n\n        grads.dq = grads.dq.reshape(dq_shape)\n        grads.dk = grads.dk.reshape(dk_shape)\n        grads.dv = grads.dv.reshape(dv_shape)\n        return grads\n"
  },
  {
    "path": "xformers/ops/fmha/dispatch.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport textwrap\nfrom collections import deque\nfrom typing import Any, List, Optional, Sequence, Tuple, Type, TypeVar\n\nimport torch\n\nfrom . import attn_bias, ck, cutlass, flash, flash3, triton_splitk\nfrom .common import AttentionBwOpBase, AttentionFwOpBase, Inputs\n\nT = TypeVar(\"T\", Type[AttentionFwOpBase], Type[AttentionBwOpBase])\n\n\n_USE_FLASH_ATTENTION_3 = True\n\n\ndef _set_use_fa3(use_flash_attention3: bool) -> None:\n    global _USE_FLASH_ATTENTION_3\n    _USE_FLASH_ATTENTION_3 = use_flash_attention3\n\n\ndef _get_use_fa3() -> bool:\n    global _USE_FLASH_ATTENTION_3\n    return _USE_FLASH_ATTENTION_3\n\n\ndef fa3_available() -> bool:\n    has_cuda = torch.version.cuda is not None\n    is_90a = has_cuda and (8, 0) <= torch.cuda.get_device_capability() <= (9, 0)\n    has_valid_flash3 = flash3._C_flashattention3 is not None  # pyre-ignore[16]\n    return is_90a and has_valid_flash3\n\n\ndef _format_inputs_description(inp: Inputs) -> str:\n    return f\"\"\"query       : shape={tuple(inp.query.shape)} ({inp.query.dtype})\nkey         : shape={tuple(inp.key.shape)} ({inp.key.dtype})\nvalue       : shape={tuple(inp.value.shape)} ({inp.value.dtype})\nattn_bias   : {type(inp.attn_bias)}\np           : {inp.p}\"\"\"\n\n\ndef _ensure_op_supports_or_raise(exc_type, name: str, op, inp: Inputs) -> None:\n    reasons = op.not_supported_reasons(inp)\n    if not reasons:\n        return\n    raise exc_type(f\"\"\"Operator `{name}` does not support inputs:\n{textwrap.indent(_format_inputs_description(inp), '     ')}\n{_format_not_supported_reasons(op, reasons)}\"\"\")\n\n\ndef _format_not_supported_reasons(op, reasons: List[str]) -> str:\n    return f\"`{op.NAME}` is not supported because:\\n    \" + \"\\n    \".join(reasons)\n\n\ndef _run_priority_list(\n    name: str,\n    priority_list: Sequence[T],\n    inp: Inputs,\n    extra_op_reasons: Optional[List[Tuple[Any, List[str]]]] = None,\n) -> T:\n    not_supported_reasons: List[List[str]] = []\n    for op in priority_list:\n        not_supported = op.not_supported_reasons(inp)\n        if not not_supported:\n            return op\n        not_supported_reasons.append(not_supported)\n\n    # Let's write a nice message explaining what we tried and why it's not supported\n    msg = f\"\"\"No operator found for `{name}` with inputs:\n{textwrap.indent(_format_inputs_description(inp), '     ')}\"\"\"\n    for op, not_supported in zip(priority_list, not_supported_reasons):\n        msg += \"\\n\" + _format_not_supported_reasons(op, not_supported)\n    if extra_op_reasons is not None:\n        for op, not_supported in extra_op_reasons:\n            msg += \"\\n\" + _format_not_supported_reasons(op, not_supported)\n    raise NotImplementedError(msg)\n\n\ndef _dispatch_fw_priority_list(\n    inp: Inputs, needs_gradient: bool\n) -> Sequence[Type[AttentionFwOpBase]]:\n    if torch.version.cuda:\n        flash3_op = [flash3.FwOp] if _get_use_fa3() else []\n        priority_list_ops = deque(\n            flash3_op\n            + [\n                flash.FwOp,\n                cutlass.FwOp,\n            ]\n        )\n    else:\n        priority_list_ops = deque(\n            [\n                ck.FwOp,\n            ]\n        )\n    if not needs_gradient:\n        mqa_or_gqa = (\n            inp.key.ndim > 3 and inp.key.stride(-2) == 0 and inp.key.shape[-2] > 1\n        )\n        # Split-KV is useful with MQA\n        # for short Q-seqlen / long K-seqlen\n        if mqa_or_gqa and inp.query.shape[1] <= 32 and inp.key.shape[1] >= 256:\n            parallelism_BH = 0  # BMK\n            if inp.query.ndim == 3:\n                parallelism_BH = inp.query.shape[0]\n            elif inp.query.ndim == 4:  # BMHK\n                parallelism_BH = inp.query.shape[0] * inp.query.shape[2]\n            elif inp.query.ndim == 5:  # BMGHK\n                parallelism_BH = inp.query.shape[0] * inp.query.shape[2]\n            if parallelism_BH > 0 and parallelism_BH < 64:\n                # priority_list_ops.appendleft(ck_splitk.FwOp)\n                priority_list_ops.appendleft(triton_splitk.FwOp)\n                # Without variable seqlen flash is fastest\n                if torch.version.cuda and not isinstance(\n                    inp.attn_bias, attn_bias.BlockDiagonalMask\n                ):\n                    if _get_use_fa3():\n                        priority_list_ops.remove(flash3.FwOp)\n                    priority_list_ops.remove(flash.FwOp)\n                    priority_list_ops.appendleft(flash.FwOp)\n\n    return priority_list_ops\n\n\ndef _dispatch_fw(inp: Inputs, needs_gradient: bool) -> Type[AttentionFwOpBase]:\n    \"\"\"Computes the best operator for forward\n\n    Raises:\n        NotImplementedError: if not operator was found\n\n    Returns:\n        AttentionOp: The best operator for the configuration\n    \"\"\"\n    return _run_priority_list(\n        \"memory_efficient_attention_forward\",\n        _dispatch_fw_priority_list(inp, needs_gradient),\n        inp,\n    )\n\n\ndef _dispatch_bw(\n    inp: Inputs, varlen_lse_packed: Optional[bool]\n) -> Type[AttentionBwOpBase]:\n    if torch.version.cuda:\n        priority_list_ops: List[Type[AttentionBwOpBase]] = [\n            flash.BwOp,\n            cutlass.BwOp,\n        ]\n        if _get_use_fa3():\n            priority_list_ops = [flash3.BwOp] + priority_list_ops\n    else:\n        priority_list_ops = [\n            ck.BwOp,\n        ]\n\n    # NOTE: If we have a variable seqlen `attn_bias`, we need to get a BW pass\n    # that supports the LSE format\n    # *unless* we are in the case where both formats are the same (bs=1)\n    extra_op_reasons = []\n    if (\n        isinstance(inp.attn_bias, attn_bias.VARLEN_BIASES)\n        and inp.attn_bias.q_seqinfo.seqstart.shape[0] > 2\n    ):\n        assert varlen_lse_packed is not None\n        for op in priority_list_ops:\n            if op.VARLEN_LSE_PACKED != varlen_lse_packed:\n                extra_op_reasons.append(\n                    (\n                        op,\n                        [\n                            f\"LSE is in {'packed' if varlen_lse_packed else 'padded'} format\"\n                        ],\n                    )\n                )\n        priority_list_ops = [\n            op for op in priority_list_ops if op.VARLEN_LSE_PACKED == varlen_lse_packed\n        ]\n    return _run_priority_list(\n        \"memory_efficient_attention_backward\", priority_list_ops, inp\n    )\n"
  },
  {
    "path": "xformers/ops/fmha/flash.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport importlib.util\nimport os\nfrom itertools import zip_longest\nfrom typing import Any, Iterable, List, Optional, Set, Tuple, Union\n\nimport torch\nfrom torch._vendor.packaging.version import parse as parse_version\n\nfrom ..common import get_operator, register_operator\nfrom .attn_bias import (\n    AttentionBias,\n    BlockDiagonalCausalFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionMask,\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    BlockDiagonalCausalMask,\n    BlockDiagonalCausalWithOffsetGappyKeysMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalLocalAttentionPaddedKeysMask,\n    BlockDiagonalMask,\n    BlockDiagonalPaddedKeysMask,\n    LocalAttentionFromBottomRightMask,\n    LowerTriangularFromBottomRightLocalAttentionMask,\n    LowerTriangularFromBottomRightMask,\n    LowerTriangularMask,\n    PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n    PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n    VARLEN_BIASES,\n)\nfrom .common import (\n    AttentionBwOpBase,\n    AttentionFwOpBase,\n    check_lastdim_alignment_stride1,\n    Context,\n    Gradients,\n    Inputs,\n)\nfrom .torch_attention_compat import ensure_pt_flash_ok\n\nFLASH_VERSION = \"0.0.0\"\n_TRY_PT_FLASH_ATTN = (\n    torch.version.hip is None and torch.backends.cuda.is_flash_attention_available()\n)\n_USE_PT_FLASH_ATTN = False\n_flash_attn_cuda = None\n\n\nif importlib.util.find_spec(\"flash_attn\"):\n    import flash_attn\n    import flash_attn.flash_attn_interface\n\n    if hasattr(flash_attn.flash_attn_interface, \"flash_attn_cuda\"):\n        _flash_attn_cuda = flash_attn.flash_attn_interface.flash_attn_cuda\n    else:\n        _flash_attn_cuda = flash_attn.flash_attn_interface.flash_attn_gpu\n\n    FLASH_VERSION = flash_attn.__version__\n    FLASH_VER_MIN = parse_version(\"2.7.1\")\n    FLASH_VER_LAST = parse_version(\"2.8.4\")  # last supported, inclusive\n    flash_ver_parsed = parse_version(FLASH_VERSION)\n    if (\n        flash_ver_parsed < FLASH_VER_MIN or flash_ver_parsed > FLASH_VER_LAST\n    ) and os.environ.get(\"XFORMERS_IGNORE_FLASH_VERSION_CHECK\", \"0\") != \"1\":\n        raise ImportError(\n            f\"Requires Flash-Attention version >={FLASH_VER_MIN},\"\n            f\"<={FLASH_VER_LAST} \"\n            f\"but got {FLASH_VERSION}.\"\n        )\n\nelif _TRY_PT_FLASH_ATTN:\n    ensure_pt_flash_ok()\n    FLASH_VERSION = torch.nn.attention._get_flash_version()  # type: ignore\n    _USE_PT_FLASH_ATTN = True\n\n\nif FLASH_VERSION != \"0.0.0\":\n\n    @torch.library.custom_op(\n        \"xformers_flash::flash_fwd\",\n        mutates_args=(),\n        device_types=[\"cuda\"],\n    )\n    def _flash_fwd(\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        cu_seqlens_q: Optional[torch.Tensor],\n        cu_seqlens_k: Optional[torch.Tensor],\n        seqused_k: Optional[torch.Tensor],\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        p: float,\n        softmax_scale: float,\n        is_causal: bool,\n        window_left: int,\n        window_right: int,\n        return_softmax: bool,\n        block_tables: Optional[torch.Tensor],\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        softcap = 0.0\n        if _USE_PT_FLASH_ATTN:\n            ret = torch.ops.aten._flash_attention_forward(\n                query,\n                key,\n                value,\n                cu_seqlens_q,  # cum_seq_q\n                cu_seqlens_k,  # cum_seq_k\n                max_seqlen_q,  # max_q\n                max_seqlen_k,  # max_k\n                p,  # dropout_p\n                is_causal,\n                return_debug_mask=False,\n                scale=softmax_scale,\n                window_size_left=window_left,\n                window_size_right=window_right,\n                seqused_k=seqused_k,\n                alibi_slopes=None,  # alibi_slopes\n            )\n            attention, logsumexp, rng_state, _, _ = ret\n            return attention, logsumexp, rng_state\n        else:\n            assert _flash_attn_cuda is not None\n            if cu_seqlens_q is None:\n                assert cu_seqlens_k is None\n                assert seqused_k is None\n                out, softmax_lse, p, rng_state = _flash_attn_cuda.fwd(\n                    query,\n                    key,\n                    value,\n                    None,  # out\n                    None,  # alibi_slopes\n                    p,\n                    softmax_scale,\n                    is_causal,\n                    window_left,  # window_size_left\n                    window_right,  # window_size_right\n                    softcap,\n                    return_softmax,\n                    None,  # rng\n                )\n            else:\n                out, softmax_lse, p, rng_state = _flash_attn_cuda.varlen_fwd(\n                    query,\n                    key,\n                    value,\n                    None,  # out\n                    cu_seqlens_q,\n                    cu_seqlens_k,\n                    seqused_k,\n                    None,  # leftpad_k_\n                    block_tables,\n                    None,  # alibi_slopes\n                    max_seqlen_q,\n                    max_seqlen_k,\n                    p,\n                    softmax_scale,\n                    False,\n                    is_causal,\n                    window_left,\n                    window_right,\n                    softcap,\n                    return_softmax,\n                    None,  # gen\n                )\n        return out, softmax_lse, rng_state\n\n    @torch.library.register_fake(\"xformers_flash::flash_fwd\")\n    def _flash_fwd_abstract(\n        query,\n        key,\n        value,\n        cu_seqlens_q,\n        cu_seqlens_k,\n        seqused_k,\n        max_seqlen_q,\n        max_seqlen_k,\n        p,\n        softmax_scale,\n        is_causal,\n        window_left,\n        window_right,\n        return_softmax,\n        block_tables,\n    ):\n        out = torch.empty_like(query)\n        if cu_seqlens_q is None:\n            B, M, H, K = query.shape\n            lse_shape = [B, H, M]\n        else:\n            M, H, K = query.shape\n            B = cu_seqlens_q.shape[0] - 1\n            lse_shape = [H, M]\n        softmax_lse = torch.empty(lse_shape, device=query.device, dtype=torch.float32)\n        rng_state = torch.empty([2], device=query.device, dtype=torch.int64)\n        return out, softmax_lse, rng_state\n\n    @torch.library.custom_op(\n        \"xformers_flash::flash_bwd\",\n        mutates_args=(),\n        device_types=[\"cuda\"],\n    )\n    def _flash_bwd(\n        grads_share_storage: bool,\n        grad: torch.Tensor,\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        out: torch.Tensor,\n        lse: torch.Tensor,\n        cu_seqlens_q: torch.Tensor,\n        cu_seqlens_k: torch.Tensor,\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        p: float,\n        softmax_scale: float,\n        is_causal: bool,\n        window_left: int,\n        window_right: int,\n        rng_state: torch.Tensor,\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        softcap = 0.0\n        if _USE_PT_FLASH_ATTN:\n            assert softcap == 0.0\n            rng_state0 = rng_state1 = rng_state\n            dq, dk, dv = torch.ops.aten._flash_attention_backward(\n                grad,\n                query,\n                key,\n                value,\n                out,\n                lse,\n                cu_seqlens_q,\n                cu_seqlens_k,\n                max_seqlen_q,\n                max_seqlen_k,\n                p,\n                is_causal,\n                rng_state0,\n                rng_state1,\n                scale=softmax_scale,\n                window_size_left=window_left,\n                window_size_right=window_right,\n            )\n        else:\n            assert _flash_attn_cuda is not None\n            dq, dk, dv = _create_dq_dk_dv(grads_share_storage, query, key, value)\n            if cu_seqlens_k is None:\n                assert cu_seqlens_q is None\n                _flash_attn_cuda.bwd(\n                    grad,\n                    query,\n                    key,\n                    value,\n                    out,\n                    lse,\n                    dq,\n                    dk,\n                    dv,\n                    None,  # alibi_slopes\n                    p,\n                    softmax_scale,\n                    is_causal,\n                    window_left,\n                    window_right,\n                    softcap,\n                    False,  # deterministic\n                    None,\n                    rng_state,\n                )\n            else:\n                _flash_attn_cuda.varlen_bwd(\n                    grad,\n                    query,\n                    key,\n                    value,\n                    out,\n                    lse,\n                    dq,\n                    dk,\n                    dv,\n                    cu_seqlens_q,\n                    cu_seqlens_k,\n                    None,  # alibi_slopes\n                    max_seqlen_q,\n                    max_seqlen_k,\n                    p,\n                    softmax_scale,\n                    False,  # zero_tensors\n                    is_causal,\n                    window_left,\n                    window_right,\n                    softcap,\n                    False,  # deterministic\n                    None,\n                    rng_state,\n                )\n        return dq, dk, dv\n\n    @torch.library.register_fake(\"xformers_flash::flash_bwd\")\n    def _flash_bwd_abstract(\n        grads_share_storage,\n        grad,\n        query,\n        key,\n        value,\n        *args,\n        **kwargs,\n    ):\n        return _create_dq_dk_dv(grads_share_storage, query, key, value)\n\n    def _create_dq_dk_dv(\n        grads_share_storage: bool, query, key, value\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        # Create dq,dk,dv\n        # If Q/K/V come from a single QKV tensor, let's put the gradient in the\n        # right strides, so we can avoid a `cat`\n        if grads_share_storage:\n            chunk = torch.empty(\n                (*query.shape[0:-2], 3, query.shape[-2], query.shape[-1]),\n                dtype=query.dtype,\n                device=query.device,\n            )\n            return chunk.select(-3, 0), chunk.select(-3, 1), chunk.select(-3, 2)\n        return torch.empty_like(query), torch.empty_like(key), torch.empty_like(value)\n\n\ndef _convert_input_format(\n    inp: Inputs,\n    supports_mqa: bool,\n    use_kvsplit: bool = False,\n) -> Tuple[\n    Inputs,\n    Optional[torch.Tensor],\n    int,\n    Optional[torch.Tensor],\n    int,\n    Optional[torch.Tensor],\n]:\n    assert inp.query.ndim in [4, 5]\n    query, key, value = inp.query, inp.key, inp.value\n    batch = query.shape[0]\n    seqlen_q = query.shape[1]\n    seqlen_kv = key.shape[1]\n    head_dim_q = query.shape[-1]\n    head_dim_v = value.shape[-1]\n\n    attn_bias = inp.attn_bias\n    if isinstance(attn_bias, BlockDiagonalMask):\n        assert attn_bias.k_seqinfo.seqstart.device == inp.query.device\n        cu_seqlen_k = attn_bias.k_seqinfo.seqstart\n        cu_seqlen_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n        seqused_k = None\n    elif isinstance(\n        attn_bias,\n        (\n            BlockDiagonalGappyKeysMask,\n            BlockDiagonalPaddedKeysMask,\n            PagedBlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalPaddedKeysMask,\n        ),\n    ):\n        assert attn_bias.k_seqinfo.seqstart.device == inp.query.device\n        cu_seqlen_k = attn_bias.k_seqinfo.seqstart\n        cu_seqlen_q = attn_bias.q_seqinfo.seqstart\n        max_seqlen_q = attn_bias.q_seqinfo.max_seqlen\n        max_seqlen_k = attn_bias.k_seqinfo.max_seqlen\n        seqused_k = attn_bias.k_seqinfo.seqlen\n    else:\n        cu_seqlen_k = None\n        cu_seqlen_q = None\n        seqused_k = None\n        max_seqlen_q = inp.query.shape[1]\n        max_seqlen_k = inp.key.shape[1]\n\n    if query.ndim == 5:  # GQA\n        assert supports_mqa\n\n        # Fold the group/head_in_group dimensions together\n        def fold(x):\n            # Either the head is replicated\n            if x.stride(3) == 0:\n                return x[:, :, :, 0]\n            # Or we reshape\n            return x.reshape(\n                [\n                    x.shape[0],\n                    x.shape[1],\n                    -1,\n                    x.shape[4],\n                ]\n            )\n\n        query = fold(query)\n        key = fold(key)\n        value = fold(value)\n    # Optimize for MHA\n    if supports_mqa and key.ndim == 4 and key.stride(2) == 0 and value.stride(2) == 0:\n        key = key[:, :, :1]\n        value = value[:, :, :1]\n    # Initially we have `query.shape = [batch, seqlen, num_heads, head_dim_q]`\n    # We want format `[batch * seqlen, num_heads, head_dim_q]`\n    if cu_seqlen_k is not None:\n        query = query.reshape([batch * seqlen_q, -1, head_dim_q])\n        key = key.reshape([batch * seqlen_kv, -1, head_dim_q])\n        value = value.reshape([batch * seqlen_kv, -1, head_dim_v])\n        if isinstance(\n            attn_bias,\n            (PagedBlockDiagonalGappyKeysMask, PagedBlockDiagonalPaddedKeysMask),\n        ):\n            num_pages = value.shape[0] // attn_bias.page_size\n            key = key.view(num_pages, attn_bias.page_size, *key.shape[1:])\n            value = value.view(num_pages, attn_bias.page_size, *value.shape[1:])\n\n    new_inp = Inputs(\n        query=query,\n        key=key,\n        value=value,\n        attn_bias=attn_bias,\n        p=inp.p,\n        scale=inp.scale,\n        output_dtype=inp.output_dtype,\n        is_partial=inp.is_partial,\n    )\n    return new_inp, cu_seqlen_q, max_seqlen_q, cu_seqlen_k, max_seqlen_k, seqused_k\n\n\ndef _is_causal(attn_bias: Optional[Union[torch.Tensor, AttentionBias]]) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            LowerTriangularMask,\n            LowerTriangularFromBottomRightMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n            BlockDiagonalCausalMask,\n            BlockDiagonalCausalLocalAttentionMask,\n            BlockDiagonalCausalFromBottomRightMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            BlockDiagonalCausalWithOffsetGappyKeysMask,\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n            PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        ),\n    )\n\n\ndef _is_paged_attention_supported(attn_bias_type) -> bool:\n    if issubclass(attn_bias_type, PagedBlockDiagonalPaddedKeysMask):\n        return not _USE_PT_FLASH_ATTN\n    if issubclass(\n        attn_bias_type,\n        (PagedBlockDiagonalGappyKeysMask, PagedBlockDiagonalPaddedKeysMask),\n    ):\n        return not torch.mtia.is_available()\n\n    return True\n\n\ndef _window_size(\n    attn_bias: Optional[Union[torch.Tensor, AttentionBias]],\n) -> Tuple[int, int]:\n    win_left = -1\n    win_right = -1\n    if isinstance(\n        attn_bias,\n        (\n            BlockDiagonalCausalLocalAttentionMask,\n            BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            LowerTriangularFromBottomRightLocalAttentionMask,\n        ),\n    ):\n        win_left = attn_bias._window_size - 1\n    if isinstance(\n        attn_bias,\n        (\n            BlockDiagonalLocalAttentionPaddedKeysMask,\n            LocalAttentionFromBottomRightMask,\n        ),\n    ):\n        win_left = attn_bias.window_left\n        win_right = attn_bias.window_right\n    return (win_left, win_right)\n\n\ndef _check_needs_no_topleft(d: Inputs, reasons: List[str]) -> None:\n    # Flash does not support TopLeft, so only allow causal masks with TopLeft\n    # if each batch element has equal number of queries and keys.\n    if isinstance(d.attn_bias, BlockDiagonalCausalMask):\n        # Flash does not support TopLeft, so only allow BlockDiagonalCausalMask\n        # if each batch element has equal number of queries and keys.\n        for k_start, q_start in zip_longest(\n            d.attn_bias.k_seqinfo.seqstart_py, d.attn_bias.q_seqinfo.seqstart_py\n        ):\n            if k_start != q_start:\n                reasons.append(\n                    \"Only support BlockDiagonalCausalMask if equal\"\n                    \" numbers of keys and queries\"\n                )\n                break\n    elif isinstance(d.attn_bias, LowerTriangularMask):\n        if d.query.shape[1] != d.key.shape[1]:\n            reasons.append(\n                \"Only support LowerTriangularMask if equal number of\" \"keys and queries\"\n            )\n\n\ndef _check_strides_for_bmghk(x: torch.Tensor, name: str, reasons: List[str]) -> None:\n    \"\"\"\n    We want to be able to collapse the G/H dimensions together\n    \"\"\"\n    if x.ndim == 5:\n        stride_g, stride_h = x.stride(2), x.stride(3)\n        if x.shape[2] == 1:\n            return\n        if x.shape[3] == 1 or stride_h == 0:\n            return\n        if stride_g != stride_h * x.shape[-2]:\n            reasons.append(\n                f\"GQA is only supported when the G/H dimensions are contiguous\\n\"\n                f\"    {name}.stride:  {x.stride()}\\n\"\n                f\"    {name}.shape :  {list(x.shape)}\"\n            )\n\n\ndef _post_process_lse(\n    lse: torch.Tensor,\n    inp: Inputs,\n    original_query_shape: Tuple[int, ...],\n) -> torch.Tensor:\n    # Easy case: no varlen\n    if not isinstance(inp.attn_bias, VARLEN_BIASES):\n        if len(original_query_shape) == 5:\n            # [B, GH, M] => [B, G, H, M]\n            return lse.unflatten(1, original_query_shape[2:4])\n        return lse\n\n    # Already packed: just bring back the batch dimension\n    if len(original_query_shape) == 5:\n        # (1, G, H, total_q)\n        return lse.unflatten(0, original_query_shape[2:4]).unsqueeze(0)\n    # (1, H, total_q)\n    return lse.unsqueeze(0)\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    \"\"\"Operator that computes memory-efficient attention using \\\n        `Flash-Attention <https://github.com/HazyResearch/flash-attention>`_ \\\n        implementation.\n    \"\"\"\n\n    OPERATOR = get_operator(\"xformers_flash\", \"flash_fwd\")\n    SUPPORTED_DEVICES: Set[str] = {\"cuda\"}\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = (8, 0)\n    SUPPORTED_DTYPES: Set[torch.dtype] = {torch.half, torch.bfloat16}\n    SUPPORTED_MAX_K = 256\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalPaddedKeysMask,\n        LocalAttentionFromBottomRightMask,\n        PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        PagedBlockDiagonalPaddedKeysMask,\n    )\n\n    SUPPORTED_ATTN_BIAS_TYPES = [\n        b for b in SUPPORTED_ATTN_BIAS_TYPES if _is_paged_attention_supported(b)\n    ]\n\n    SUPPORTS_DROPOUT = True\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_DIFFERENT_VALUE_EMBED = False\n    SUPPORTS_BMGHK = True\n    SUPPORTS_PARTIAL = True\n    VARLEN_LSE_PACKED = True\n    NAME = f\"fa2F@{FLASH_VERSION}-pt\" if _USE_PT_FLASH_ATTN else f\"fa2F@{FLASH_VERSION}\"\n    VERSION = FLASH_VERSION\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        _check_needs_no_topleft(d, reasons)\n        _check_strides_for_bmghk(d.query, \"query\", reasons)\n        _check_strides_for_bmghk(d.key, \"key\", reasons)\n        _check_strides_for_bmghk(d.value, \"value\", reasons)\n\n        return reasons\n\n    @classmethod\n    def apply(\n        cls, inp: Inputs, needs_gradient: bool\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        return_softmax = False\n        original_query_shape = inp.query.shape\n\n        out_shape = [\n            *inp.query.shape[:-1],\n            inp.value.shape[-1],\n        ]\n        # no cumulative seqlen\n        (\n            inp,\n            cu_seqlens_q,\n            max_seqlen_q,\n            cu_seqlens_k,\n            max_seqlen_k,\n            seqused_k,\n        ) = _convert_input_format(inp, supports_mqa=True)\n\n        if inp.query.numel() > 0 and inp.key.numel() > 0:\n            win_left, win_right = _window_size(inp.attn_bias)\n            block_tables = (\n                inp.attn_bias.block_tables\n                if isinstance(inp.attn_bias, PagedBlockDiagonalPaddedKeysMask)\n                else None\n            )\n            out, softmax_lse, rng_state = cls.OPERATOR(\n                inp.query,\n                inp.key,\n                inp.value,\n                cu_seqlens_q,\n                cu_seqlens_k,\n                seqused_k,\n                max_seqlen_q,\n                max_seqlen_k,\n                inp.p,\n                inp.scale_float,\n                _is_causal(inp.attn_bias),\n                window_left=win_left,\n                window_right=win_right,\n                return_softmax=return_softmax,\n                block_tables=block_tables,\n            )\n            out = out.reshape(out_shape)\n        else:\n            out = torch.zeros(out_shape, device=inp.query.device, dtype=inp.query.dtype)\n            rng_state = None\n            lse_shape = (\n                [inp.query.shape[2], inp.query.shape[0] * inp.query.shape[1]]\n                if isinstance(inp.attn_bias, VARLEN_BIASES)\n                else [inp.query.shape[0], inp.query.shape[2], inp.query.shape[1]]\n            )\n            if inp.is_partial:\n                softmax_lse = torch.full(\n                    lse_shape,\n                    float(\"-inf\"),\n                    device=inp.query.device,\n                    dtype=torch.float32,\n                )\n            else:\n                softmax_lse = torch.empty(\n                    lse_shape,\n                    device=inp.query.device,\n                    dtype=torch.float32,\n                )\n\n        if not needs_gradient:\n            return out, None\n        ctx = Context(\n            out=out,\n            lse=_post_process_lse(softmax_lse, inp, original_query_shape),\n        )\n        if inp.p != 0.0:\n            ctx.op_bw = BwOp\n            ctx.rng_state = rng_state\n        return (out, ctx)\n\n\n@register_operator\nclass BwOp(AttentionBwOpBase):\n    __doc__ = FwOp.__doc__\n\n    OPERATOR = get_operator(\"xformers_flash\", \"flash_bwd\")\n    SUPPORTED_DEVICES = FwOp.SUPPORTED_DEVICES\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = FwOp.CUDA_MINIMUM_COMPUTE_CAPABILITY\n    SUPPORTED_DTYPES = FwOp.SUPPORTED_DTYPES\n    SUPPORTED_MAX_K = FwOp.SUPPORTED_MAX_K\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = tuple(\n        set(FwOp.SUPPORTED_ATTN_BIAS_TYPES).difference(\n            {\n                BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n                BlockDiagonalCausalWithOffsetGappyKeysMask,\n                BlockDiagonalCausalWithOffsetPaddedKeysMask,\n                BlockDiagonalGappyKeysMask,\n                BlockDiagonalPaddedKeysMask,\n                PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n                PagedBlockDiagonalPaddedKeysMask,\n            }\n        )\n    )\n    SUPPORTS_DROPOUT = FwOp.SUPPORTS_DROPOUT\n    SUPPORTS_CUSTOM_SCALE = FwOp.SUPPORTS_CUSTOM_SCALE\n    SUPPORTS_DIFFERENT_VALUE_EMBED = FwOp.SUPPORTS_DIFFERENT_VALUE_EMBED\n    IS_DETERMINISTIC = False\n    SUPPORTS_BMGHK = False  # NOTE: Don't forget to update fmha doc when changing this!\n    VARLEN_LSE_PACKED = True\n    NAME = f\"fa2B@{FLASH_VERSION}-pt\" if _USE_PT_FLASH_ATTN else f\"fa2B@{FLASH_VERSION}\"\n    VERSION = FLASH_VERSION\n\n    MAX_HEADDIM_DROPOUT_SM8x = 224\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(BwOp, cls).not_supported_reasons(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        _check_needs_no_topleft(d, reasons)\n        if d.device.type == \"cuda\":\n            # Due to limited shared-memory, some GPUs are limited in head dimension\n            device_capability = torch.cuda.get_device_capability(d.device)\n            is_sm80_or_sm90 = device_capability in [(8, 0), (9, 0)]\n            if (\n                max(d.key.shape[-1], d.query.shape[-1]) > cls.MAX_HEADDIM_DROPOUT_SM8x\n                and not is_sm80_or_sm90\n                and d.p != 0.0\n            ):\n                reasons.append(\n                    \"requires a GPU with compute capability 8.0 \"\n                    f\"(A100) or 9.0 (H100) for dropout when 'query.shape[-1] > {cls.MAX_HEADDIM_DROPOUT_SM8x}'\"\n                )\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        dq_shape, dk_shape, dv_shape = inp.query.shape, inp.key.shape, inp.value.shape\n        (\n            inp,\n            cu_seqlens_q,\n            max_seqlen_q,\n            cu_seqlens_k,\n            max_seqlen_k,\n            seqused_k,\n        ) = _convert_input_format(inp, supports_mqa=False)\n        # assert ctx.lse.is_contiguous()\n        assert seqused_k is None\n        ctx_lse = ctx.lse\n        if isinstance(inp.attn_bias, VARLEN_BIASES):\n            assert ctx_lse.shape[0] == 1\n            ctx_lse = ctx_lse[0]\n        else:\n            # NOTE: cutlass pads the last dimension, we need to slice it\n            assert ctx_lse.shape[2] >= max_seqlen_q\n            ctx_lse = ctx_lse[:, :, :max_seqlen_q].contiguous()\n        kernel_out_shape = [\n            *inp.query.shape[:-1],\n            inp.value.shape[-1],\n        ]\n        assert grad.dtype in cls.SUPPORTED_DTYPES\n\n        if inp.query.numel() and inp.key.numel():\n            win_left, win_right = _window_size(inp.attn_bias)\n            grads = Gradients(\n                *cls.OPERATOR(\n                    ctx.qkv_share_storage,\n                    grad.reshape(kernel_out_shape).contiguous(),\n                    inp.query,\n                    inp.key,\n                    inp.value,\n                    ctx.out.reshape(kernel_out_shape),\n                    ctx_lse,\n                    cu_seqlens_q,\n                    cu_seqlens_k,\n                    max_seqlen_q,\n                    max_seqlen_k,\n                    inp.p,\n                    inp.scale_float,\n                    _is_causal(inp.attn_bias),\n                    window_left=win_left,\n                    window_right=win_right,\n                    rng_state=ctx.rng_state if inp.p > 0.0 else None,\n                )\n            )\n        else:\n            grads = Gradients(\n                dq=torch.zeros_like(inp.query),\n                dk=torch.zeros_like(inp.key),\n                dv=torch.zeros_like(inp.value),\n            )\n        if grads.dq.numel() == 0:\n            grads.dk.zero_()\n            grads.dv.zero_()\n        if grads.dv.numel() == 0:\n            grads.dq.zero_()\n        grads.dq = grads.dq.reshape(dq_shape)\n        grads.dk = grads.dk.reshape(dk_shape)\n        grads.dv = grads.dv.reshape(dv_shape)\n        return grads\n"
  },
  {
    "path": "xformers/ops/fmha/flash3.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport importlib.util\nimport logging\nimport os\nfrom typing import Any, Iterable, List, Optional, Sequence, Set, Tuple, TypeVar\n\nimport torch\nfrom torch._C import parse_schema\nfrom torch.utils.flop_counter import (\n    _unpack_flash_attention_nested_shapes,\n    register_flop_formula,\n)\n\nfrom ..common import get_operator, register_operator\nfrom .attn_bias import (\n    BlockDiagonalCausalFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n    BlockDiagonalCausalLocalAttentionMask,\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    BlockDiagonalCausalMask,\n    BlockDiagonalCausalWithOffsetGappyKeysMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalLocalAttentionPaddedKeysMask,\n    BlockDiagonalMask,\n    BlockDiagonalPaddedKeysMask,\n    LocalAttentionFromBottomRightMask,\n    LowerTriangularFromBottomRightLocalAttentionMask,\n    LowerTriangularFromBottomRightMask,\n    LowerTriangularMask,\n    PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n    PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n    VARLEN_BIASES,\n)\nfrom .common import (\n    AttentionBwOpBase,\n    AttentionFwOpBase,\n    check_lastdim_alignment_stride1,\n    Context,\n    Gradients,\n    Inputs,\n    ScaledTensor,\n)\nfrom .flash import (\n    _check_needs_no_topleft,\n    _convert_input_format,\n    _is_causal,\n    _post_process_lse,\n    _window_size,\n)\n\nFLASH_VERSION = \"0.0.0\"\nlogger = logging.getLogger(__name__)\n\nT = TypeVar(\"T\")\n\n\ndef maybe_contiguous(x: T) -> T:\n    return x.contiguous() if x is not None and x.stride(-1) != 1 else x  # type: ignore[attr-defined]\n\n\ndef _flash_attention3_incompatible_reason() -> Optional[str]:\n    if not hasattr(torch.ops.flash_attn_3, \"fwd\") or not hasattr(\n        torch.ops.flash_attn_3, \"bwd\"\n    ):\n        return \"PyTorch has no `flash_attn_3` - is your Flash-Attention version recent enough?\"\n    if not torch.ops.flash_attn_3.fwd.default._schema.is_backward_compatible_with(  # type: ignore\n        parse_schema(\n            \"flash_attn_3::fwd(Tensor q, Tensor k, Tensor v, Tensor(k_new!)? k_new=None, \"\n            \"Tensor(v_new!)? v_new=None, Tensor? q_v=None, Tensor(out!)? out=None, \"\n            \"Tensor? cu_seqlens_q=None, Tensor? cu_seqlens_k=None, \"\n            \"Tensor? cu_seqlens_k_new=None, Tensor? seqused_q=None, Tensor? seqused_k=None, \"\n            \"int? max_seqlen_q=None, int? max_seqlen_k=None, Tensor? page_table=None, \"\n            \"Tensor? kv_batch_idx=None, Tensor? leftpad_k=None, Tensor? rotary_cos=None, Tensor? rotary_sin=None, \"\n            \"Tensor? seqlens_rotary=None, Tensor? q_descale=None, Tensor? k_descale=None, Tensor? v_descale=None, \"\n            \"float? softmax_scale=None, bool is_causal=False, int window_size_left=-1, int window_size_right=-1, \"\n            \"int attention_chunk=0, float softcap=0., bool is_rotary_interleaved=False, \"\n            \"Tensor? scheduler_metadata=None, int num_splits=0, bool? pack_gqa=None, int sm_margin=0) \"\n            \"-> (Tensor(out!), Tensor, Tensor, Tensor)\"\n        )\n    ):\n        return \"flash_attn_3::fwd operator is not compatible\"\n    if not torch.ops.flash_attn_3.bwd.default._schema.is_backward_compatible_with(  # type: ignore\n        parse_schema(\n            \"flash_attn_3::bwd(Tensor dout, Tensor q, Tensor k, Tensor v, Tensor out, Tensor softmax_lse, \"\n            \"Tensor(dq!)? dq=None, Tensor(dk!)? dk=None, Tensor(dv!)? dv=None, Tensor? cu_seqlens_q=None, \"\n            \"Tensor? cu_seqlens_k=None, Tensor? seqused_q=None, Tensor? seqused_k=None, int? max_seqlen_q=None, \"\n            \"int? max_seqlen_k=None, float? softmax_scale=None, bool is_causal=False, int window_size_left=-1, \"\n            \"int window_size_right=-1, float softcap=0., bool deterministic=False, int sm_margin=0) \"\n            \"-> (Tensor(dq!), Tensor(dk!), Tensor(dv!), Tensor, Tensor, Tensor, Tensor, Tensor)\"\n        )\n    ) and not torch.ops.flash_attn_3.bwd.default._schema.is_backward_compatible_with(  # type: ignore\n        parse_schema(\n            \"flash_attn_3::bwd(Tensor dout, Tensor q, Tensor k, Tensor v, Tensor out, Tensor softmax_lse, \"\n            \"Tensor(dq!)? dq=None, Tensor(dk!)? dk=None, Tensor(dv!)? dv=None, Tensor? cu_seqlens_q=None, \"\n            \"Tensor? cu_seqlens_k=None, Tensor? seqused_q=None, Tensor? seqused_k=None, int? max_seqlen_q=None, \"\n            \"int? max_seqlen_k=None, float? softmax_scale=None, bool is_causal=False, int window_size_left=-1, \"\n            \"int window_size_right=-1, float softcap=0., bool deterministic=False, int sm_margin=0) \"\n            \"-> (Tensor, Tensor, Tensor, Tensor, Tensor)\"\n        )\n    ):\n        return \"flash_attn_3::bwd operator is not compatible\"\n    return None\n\n\nFLASH3_HAS_PAGED_ATTENTION = True\nFLASH3_HAS_FLOAT8 = False\nFLASH3_HAS_DETERMINISTIC_MODE = False\n_C_flashattention3 = None\nif importlib.util.find_spec(\"flash_attn_3\") and importlib.util.find_spec(\n    \"flash_attn_3._C\"\n):\n    import flash_attn_3._C  # type: ignore[attr-defined]  # noqa: F401\n\n    incompat_reason = _flash_attention3_incompatible_reason()\n    if incompat_reason is None:\n        _C_flashattention3 = torch.ops.flash_attn_3\n        FLASH_VERSION = \"pip_pkg\"\n        FLASH3_HAS_PAGED_ATTENTION = True\n        FLASH3_HAS_FLOAT8 = True\n    else:\n        logger.warning(f\"Flash-Attention 3 package can't be used: {incompat_reason}\")\n\n\ndef _heuristic_kvsplit(\n    inp: Inputs,\n    enable_kvsplit_attn: bool,\n) -> bool:\n    atten_bias = inp.attn_bias\n\n    # make sure Q doesn't have varlen\n    # pyre-ignore Undefined attribute [16]\n    if atten_bias.q_seqinfo.min_seqlen != atten_bias.q_seqinfo.max_seqlen:  # type: ignore[union-attr]\n        return False\n\n    # filter out prefill case\n    # pyre-ignore Undefined attribute [16]\n    if atten_bias.q_seqinfo.max_seqlen == atten_bias.k_seqinfo.max_seqlen:  # type: ignore[union-attr]\n        return False\n\n    return enable_kvsplit_attn\n\n\ndef mask_non_zeros(s_q: int, s_k: int, window_left: int, window_right: int) -> int:\n    # Exact formula for easy cases\n    if window_left < 0 and window_right < 0:  # full\n        return s_q * s_k\n    if window_left < 0 and window_right == 0:  # causal\n        # (from bottom right)\n        return (s_q * (s_q + 1)) // 2 + s_q * max(0, s_k - s_q)\n\n    # NOTE: Flops calculations here assume `s_q == s_k`\n    # otherwise the local attention computations are too involved\n    # See also https://docs.google.com/spreadsheets/d/1u1ItCZcHLArcqXLj7mwR4H1pI3lMKU1zlxCYi8JCYgk/edit?usp=sharing\n    if window_left < 0:\n        window_left = s_k\n    if window_right < 0:\n        window_right = s_k\n\n    # below the diagonal\n    # ┌───────┐\n    # │ ╲     │\n    # │  ╲    │ <- Upper triangle (\"ut\")\n    # │┄┄┄╲   │ <--- `lastq_ut`\n    # │╲   ╲  │\n    # │ ╲   ╲ │ <- Lower part\n    # │  ╲   ╲│\n    # └───────┘\n    mask_nz = min(s_q, s_k)  # diagonal\n    # Below diagonal (with `window_left`)\n    lastq_ut = min(window_left, s_q)\n    mask_nz += ((lastq_ut - 1) * lastq_ut) // 2  # upper triangle\n    mask_nz += (s_q - lastq_ut) * window_left  # lower part\n    # Above diagonal (with `window_right`)\n    # (counting rows from the bottom for symmetry)\n    firstq_bt = min(window_right + 1, s_q)\n    mask_nz += ((firstq_bt - 1) * firstq_bt) // 2  # bottom triangle\n    mask_nz += (s_q - firstq_bt) * window_right\n\n    return mask_nz\n\n\n# Copied from PyTorch, modified to support MQA/GQA and local attention\n# No need to take care of this for the bwd because we don't \"unexpand\" the keys\n# and values (in the fwd we expand to help with the seqlen/headdim swap trick).\ndef sdpa_flop_count(\n    query_shape, key_shape, value_shape, window_left: int, window_right: int\n):\n    \"\"\"\n    Count flops for self-attention.\n\n    NB: We can assume that value_shape == key_shape\n    \"\"\"\n    b, h_q, s_q, d_q = query_shape\n    _b2, h_kv, s_k, _d2 = key_shape\n    _b3, _h2, _s3, d_v = value_shape\n    assert b == _b2 == _b3\n    assert h_kv == _h2\n    assert d_q == _d2\n    assert s_k == _s3\n    assert d_q == _d2\n    assert h_q % h_kv == 0\n    # How many values are computed in the attention?\n    mask_nz = mask_non_zeros(s_q, s_k, window_left, window_right)\n\n    # q@k.T\n    total_flops = 2 * b * h_q * d_q * mask_nz\n    # attn@v\n    total_flops += 2 * b * h_q * d_v * mask_nz\n    return total_flops\n\n\nif _C_flashattention3 is not None:\n    # returns: out, q_padded, k_padded, v_padded, out_padded, softmax_lse, p\n    @torch.library.custom_op(\n        \"xformers_flash3::flash_fwd\", mutates_args=(), device_types=[\"cuda\"]\n    )\n    def mha_fwd(\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        cu_seqlens_q: Optional[torch.Tensor],\n        cu_seqlens_k: Optional[torch.Tensor],\n        seqused_k: Optional[torch.Tensor],\n        leftpad_k: Optional[torch.Tensor],\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        p: float,\n        softmax_scale: float,\n        is_causal: bool,\n        descale_q: Optional[torch.Tensor] = None,\n        descale_k: Optional[torch.Tensor] = None,\n        descale_v: Optional[torch.Tensor] = None,\n        block_table: Optional[torch.Tensor] = None,\n        use_kvsplit: bool = False,\n        window_left: int = -1,\n        window_right: int = -1,\n    ) -> Tuple[torch.Tensor, torch.Tensor]:\n        query, key = [maybe_contiguous(x) for x in (query, key)]\n        value = (\n            value.contiguous()\n            if value.stride(-1) != 1 and value.stride(-3) != 1\n            else value\n        )\n        cu_seqlens_q, cu_seqlens_k, seqused_k = [\n            maybe_contiguous(x) for x in (cu_seqlens_q, cu_seqlens_k, seqused_k)\n        ]\n        block_table = maybe_contiguous(block_table)\n\n        def _get_batch():\n            if cu_seqlens_q is not None:\n                return cu_seqlens_q.shape[0] - 1\n            return query.shape[0]\n\n        is_paged = block_table is not None\n        bs = _get_batch()\n        orig_query_shape = query.shape\n\n        pack_gqa = None\n        if use_kvsplit:\n            # For KV split, we need to make sure query in shape [batch, seqlen, num_heads, head_dim_q]\n            # to be compatible with `pack_gqa` feature\n            query = query.view(bs, -1, query.shape[-2], query.shape[-1])\n            cu_seqlens_q = None\n\n            # Auto-detect if we should use GQA parallel mode\n            if query.shape[1] <= 64 and query.shape[2] != key.shape[2]:\n                pack_gqa = True\n\n        assert _C_flashattention3 is not None\n        out, softmax_lse, *rest = _C_flashattention3.fwd(\n            query,\n            key,\n            value,\n            None,\n            None,  # k_new, v_new\n            None,  # qv\n            None,  # out\n            cu_seqlens_q,\n            cu_seqlens_k if not is_paged else None,\n            None,  # cu_seqlens_k_new\n            None,  # seqused_q\n            seqused_k,\n            max_seqlen_q,\n            max_seqlen_k,\n            block_table,\n            None,  # kv_batch_idx\n            leftpad_k,\n            None,  # rotary_cos\n            None,  # rotary_sin\n            None,  # seqlens_rotary\n            descale_q,\n            descale_k,\n            descale_v,\n            softmax_scale,\n            is_causal,\n            window_left,\n            window_right,\n            0,  # attention_chunk\n            0.0,  # softcap\n            not use_kvsplit,  # rotary_interleaved\n            None,  # scheduler_metadata\n            1 if not use_kvsplit else 0,  # num_splits\n            pack_gqa,  # pack_gqa\n            0,  # sm_margin\n        )\n\n        if query.shape != orig_query_shape:\n            # Reshape softmax_lse to match expected output format\n            num_heads_q = query.shape[-2]\n            orig_lse_shape = softmax_lse.shape\n            softmax_lse = softmax_lse.view(\n                orig_lse_shape[0], num_heads_q, -1, orig_lse_shape[2]\n            )\n            softmax_lse = softmax_lse.permute(1, 0, 2, 3).reshape(num_heads_q, -1)\n\n        return out, softmax_lse\n\n    @torch.library.register_fake(\"xformers_flash3::flash_fwd\")\n    def mha_fwd_fake(\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        cu_seqlens_q: Optional[torch.Tensor],\n        cu_seqlens_k: Optional[torch.Tensor],\n        seqused_k: Optional[torch.Tensor],\n        leftpad_k: Optional[torch.Tensor],\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        p: float,\n        softmax_scale: float,\n        is_causal: bool,\n        descale_q: Optional[torch.Tensor] = None,\n        descale_k: Optional[torch.Tensor] = None,\n        descale_v: Optional[torch.Tensor] = None,\n        block_table: Optional[torch.Tensor] = None,\n        use_kvsplit: bool = False,\n        window_left: int = -1,\n        window_right: int = -1,\n    ) -> Tuple[torch.Tensor, torch.Tensor]:\n        query_shape = query.shape\n        out_shape = (*query_shape[:-1], value.shape[-1])\n        if query.dtype == torch.float8_e4m3fn or query.dtype == torch.float8_e5m2:\n            out = query.new_empty(out_shape, dtype=torch.bfloat16)\n        else:\n            out = query.new_empty(out_shape)\n        # Query is (B, M, H, K) or (total_M, H, K)\n        # LSE is (B, H, M) or (H, total_M)\n        lse_shape = (\n            (query_shape[0], query_shape[2], query_shape[1])\n            if cu_seqlens_q is None\n            else (query_shape[1], query_shape[0])\n        )\n        lse = query.new_empty(lse_shape, dtype=torch.float32)\n        return out, lse\n\n    @register_flop_formula(torch.ops.xformers_flash3.flash_fwd, get_raw=True)\n    def mha_fwd_flops(\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        cu_seqlens_q: Optional[torch.Tensor],\n        cu_seqlens_k: Optional[torch.Tensor],\n        seqused_k: Optional[torch.Tensor],\n        leftpad_k: Optional[torch.Tensor],\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        p: float,\n        softmax_scale: float,\n        is_causal: bool,\n        descale_q: Optional[torch.Tensor] = None,\n        descale_k: Optional[torch.Tensor] = None,\n        descale_v: Optional[torch.Tensor] = None,\n        block_table: Optional[torch.Tensor] = None,\n        use_kvsplit: bool = False,\n        window_left: int = -1,\n        window_right: int = -1,\n        # The FLOPs counter might pass more args (out_val, out_shape, ...)\n        *args,\n        **kwargs,\n    ):\n        assert 3 <= query.ndim <= 4\n        assert 3 <= key.ndim <= 4\n        assert 3 <= value.ndim <= 4\n        # This FLOP formula is used by torch.compile's partitioner \"automatic\n        # activation checkpointing\" (AutoAC) to decide which ops to preserve\n        # for backward or to recompute. However, this formula is data-dependent!\n        # This makes all invocations reuse the choices made based on the first\n        # inputs, which may be sub-optimal but also lead to inconsistent\n        # behavior across runs. In the presence of tensor parallelism it might\n        # also lead to deadlocks if AutoAC recomputes different collectives\n        # on different ranks. For distributed jobs it seems more robust to have\n        # all ranks always use the \"worst case\" FLOP estimate. Ranks are in\n        # lockstep anyways and will be going as fast as the slowest one.\n        if os.environ.get(\"XFORMERS_FLOP_FORMULA_WORST_CASE\", \"0\") == \"1\":\n            cu_seqlens_q = cu_seqlens_k = max_seqlen_q = max_seqlen_k = None  # type: ignore[assignment]\n            query = query.unsqueeze(0) if query.ndim == 3 else query\n            key = key.unsqueeze(0) if key.ndim == 3 else key\n            value = value.unsqueeze(0) if value.ndim == 3 else value\n        sizes = _unpack_flash_attention_nested_shapes(\n            query=query.transpose(-2, -3) if query.ndim == 4 else query,\n            key=key.transpose(-2, -3) if key.ndim == 4 else key,\n            value=value.transpose(-2, -3) if value.ndim == 4 else value,\n            cum_seq_q=cu_seqlens_q,\n            cum_seq_k=cu_seqlens_k,\n            max_q=max_seqlen_q,\n            max_k=max_seqlen_k,\n        )\n        if is_causal:\n            window_right = 0\n        res = sum(\n            sdpa_flop_count(\n                query_shape,\n                key_shape,\n                value_shape,\n                window_left=window_left,\n                window_right=window_right,\n            )\n            for query_shape, key_shape, value_shape, _ in sizes\n        )\n        return res\n\n    def _create_dq_dk_dv(\n        grads_share_storage: bool, query, key, value\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        # Create dq,dk,dv\n        # If Q/K/V come from a single QKV tensor, let's put the gradient in the\n        # right strides, so we can avoid a `cat`\n        if grads_share_storage:\n            chunk = torch.empty(\n                (*query.shape[0:-2], 3, query.shape[-2], query.shape[-1]),\n                dtype=query.dtype,\n                device=query.device,\n            )\n            return chunk.select(-3, 0), chunk.select(-3, 1), chunk.select(-3, 2)\n        return torch.empty_like(query), torch.empty_like(key), torch.empty_like(value)\n\n    @torch.library.custom_op(\n        \"xformers_flash3::flash_bwd\", mutates_args=(), device_types=[\"cuda\"]\n    )\n    def mha_bwd(\n        grads_share_storage: bool,\n        dout: torch.Tensor,\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        out: torch.Tensor,\n        softmax_lse: torch.Tensor,\n        cu_seqlens_q: torch.Tensor,\n        cu_seqlens_k: torch.Tensor,\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        softmax_scale: float,\n        is_causal: bool,\n        window_left: int,\n        window_right: int,\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        dq, dk, dv = _create_dq_dk_dv(grads_share_storage, query, key, value)\n        is_deterministic = (\n            torch.are_deterministic_algorithms_enabled()\n            and FLASH3_HAS_DETERMINISTIC_MODE\n        )\n        if cu_seqlens_q is None:\n            assert cu_seqlens_k is None\n\n        assert _C_flashattention3 is not None\n        _C_flashattention3.bwd(\n            dout,\n            query,\n            key,\n            value,\n            out,\n            softmax_lse,\n            dq,\n            dk,\n            dv,\n            cu_seqlens_q,\n            cu_seqlens_k,\n            None,  # seqused_q\n            None,  # seqused_k\n            max_seqlen_q,\n            max_seqlen_k,\n            softmax_scale,\n            is_causal,\n            window_left,\n            window_right,\n            0.0,  # not used, softcap\n            is_deterministic,\n            0,  # not used, sm_margin\n        )\n        return dq, dk, dv\n\n    @torch.library.register_fake(\"xformers_flash3::flash_bwd\")\n    def mha_bwd_fake(\n        grads_share_storage: bool,\n        dout: torch.Tensor,\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        out: torch.Tensor,\n        softmax_lse: torch.Tensor,\n        cu_seqlens_q: torch.Tensor,\n        cu_seqlens_k: torch.Tensor,\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        softmax_scale: float,\n        is_causal: bool,\n        window_left: int,\n        window_right: int,\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        return _create_dq_dk_dv(grads_share_storage, query, key, value)\n\n    @register_flop_formula(torch.ops.xformers_flash3.flash_bwd, get_raw=True)\n    def mha_bwd_flops(\n        grads_share_storage: bool,\n        dout: torch.Tensor,\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        out: torch.Tensor,\n        softmax_lse: torch.Tensor,\n        cu_seqlens_q: torch.Tensor,\n        cu_seqlens_k: torch.Tensor,\n        max_seqlen_q: int,\n        max_seqlen_k: int,\n        softmax_scale: float,\n        is_causal: bool,\n        window_left: int,\n        window_right: int,\n        # The FLOPs counter might pass more args (out_val, out_shape, ...)\n        *args,\n        **kwargs,\n    ):\n        return (\n            5\n            * mha_fwd_flops(\n                query,\n                key,\n                value,\n                cu_seqlens_q=cu_seqlens_q,\n                cu_seqlens_k=cu_seqlens_k,\n                seqused_k=None,\n                leftpad_k=None,\n                max_seqlen_q=max_seqlen_q,\n                max_seqlen_k=max_seqlen_k,\n                p=0.0,\n                softmax_scale=1.0,\n                is_causal=is_causal,\n                descale_q=None,\n                descale_k=None,\n                descale_v=None,\n                block_table=None,\n                use_kvsplit=False,\n                window_left=window_left,\n                window_right=window_right,\n            )\n            // 2\n        )\n\n\ndef _check_different_value_headdim_ampere(d: Inputs, reasons: List[str]) -> None:\n    if (\n        d.query.device.type == \"cuda\"\n        and (torch.version.hip is None)\n        and d.query.shape[-1] != d.value.shape[-1]\n    ):\n        device_capability = torch.cuda.get_device_capability(d.device)\n        if device_capability < (9, 0):\n            reasons.append(\n                f\"Q/K head-dim ({d.query.shape[-1]}) must be equal \"\n                f\"to V head-dim ({d.value.shape[-1]}) for Ampere GPUs\"\n            )\n\n\ndef _get_blocktables(inp_attn_bias) -> Optional[torch.Tensor]:\n    return (\n        inp_attn_bias.block_tables\n        if isinstance(\n            inp_attn_bias,\n            (PagedBlockDiagonalGappyKeysMask, PagedBlockDiagonalPaddedKeysMask),\n        )\n        else None\n    )\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    \"\"\"Operator that computes memory-efficient attention using \\\n        `Flash-Attention <https://github.com/HazyResearch/flash-attention>`_ \\\n        implementation.\n    \"\"\"\n\n    OPERATOR = get_operator(\"xformers_flash3\", \"flash_fwd\")\n    SUPPORTED_DEVICES: Set[str] = {\"cuda\"}\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = (8, 0)\n    CUDA_MAXIMUM_COMPUTE_CAPABILITY = (9, 0)\n    SUPPORTED_DTYPES: Set[torch.dtype] = {\n        torch.half,\n        torch.bfloat16,\n    } | ({torch.float8_e4m3fn} if FLASH3_HAS_FLOAT8 else set())\n    SUPPORTED_MAX_K = 256\n    SUPPORTED_MIN_K = 32\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalLocalAttentionPaddedKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalPaddedKeysMask,\n        LocalAttentionFromBottomRightMask,\n    ) + (\n        (\n            PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n            PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n            PagedBlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalPaddedKeysMask,\n        )\n        if FLASH3_HAS_PAGED_ATTENTION\n        else tuple()\n    )\n\n    SUPPORTS_DROPOUT = False\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_DIFFERENT_VALUE_EMBED = True  # Only hopper\n    SUPPORTS_BMGHK = True\n    SUPPORTS_PARTIAL = True\n    UNPADDED_LSE = True\n    NAME = f\"fa3F@{FLASH_VERSION}\"\n    VERSION = FLASH_VERSION\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        check_lastdim_alignment_stride1(reasons, \"key\", d.value, 8)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, 8)\n        _check_needs_no_topleft(d, reasons)\n        _check_different_value_headdim_ampere(d, reasons)\n        if (\n            _get_blocktables(d.attn_bias) is not None\n            and d.query.shape[-1] != d.value.shape[-1]\n        ):\n            reasons.append(\n                f\"Q/K head-dim ({d.query.shape[-1]}) must be equal \"\n                f\"to V head-dim ({d.value.shape[-1]}) for paged attention\"\n            )\n        return reasons\n\n    @classmethod\n    def apply(\n        cls,\n        inp: Inputs,\n        needs_gradient: bool,\n        use_kvsplit: bool = False,\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        original_query_shape = inp.query.shape\n        out_shape = [\n            *inp.query.shape[:-1],\n            inp.value.shape[-1],\n        ]\n\n        def unpack_func(x) -> Tuple[torch.Tensor, Any]:\n            return x.unpack() if isinstance(x, ScaledTensor) else (x, None)\n\n        inp.query, descale_q = unpack_func(inp.query)\n        inp.key, descale_k = unpack_func(inp.key)\n        inp.value, descale_v = unpack_func(inp.value)\n        (\n            inp,\n            cu_seqlens_q,\n            max_seqlen_q,\n            cu_seqlens_k,\n            max_seqlen_k,\n            seqused_k,\n        ) = _convert_input_format(inp, supports_mqa=True, use_kvsplit=use_kvsplit)\n\n        q = inp.query\n        k = inp.key\n        v = inp.value\n\n        if inp.query.numel() > 0 and inp.key.numel() > 0:\n            win_left, win_right = _window_size(inp.attn_bias)\n            block_tables = _get_blocktables(inp.attn_bias)\n            leftpad_k = None\n            if isinstance(inp.attn_bias, PagedBlockDiagonalGappyKeysMask):\n                assert cu_seqlens_q is not None\n                assert cu_seqlens_k is not None\n                if len(cu_seqlens_q) == len(cu_seqlens_k):\n                    # case #1: len(cu_seqlens_k) = batch_size + 1\n                    leftpad_k = cu_seqlens_k[:-1]\n                else:\n                    # case #2: len(cu_seqlens_k) = batch_size\n                    assert (\n                        len(cu_seqlens_q) - len(cu_seqlens_k) == 1\n                    ), f\"{len(cu_seqlens_q)=} {len(cu_seqlens_k)=}\"\n                    leftpad_k = cu_seqlens_k\n            out, softmax_lse = cls.OPERATOR(\n                q,\n                k,\n                v,\n                cu_seqlens_q=cu_seqlens_q,\n                cu_seqlens_k=cu_seqlens_k,\n                seqused_k=seqused_k,\n                leftpad_k=leftpad_k,\n                max_seqlen_q=max_seqlen_q,\n                max_seqlen_k=max_seqlen_k,\n                p=inp.p,\n                softmax_scale=inp.scale_float,\n                is_causal=_is_causal(inp.attn_bias),\n                descale_q=descale_q,\n                descale_k=descale_k,\n                descale_v=descale_v,\n                block_table=block_tables,\n                use_kvsplit=use_kvsplit,\n                window_left=win_left,\n                window_right=win_right,\n            )\n            out = out.reshape(out_shape)\n        else:\n            out = torch.zeros(\n                inp.query.shape, device=inp.query.device, dtype=inp.query.dtype\n            )\n            if inp.is_partial:\n                softmax_lse = torch.full(\n                    [inp.query.shape[0], inp.query.shape[2], inp.query.shape[1]],\n                    float(\"-inf\"),\n                    device=inp.query.device,\n                    dtype=torch.float32,\n                )\n            else:\n                softmax_lse = torch.empty(\n                    [inp.query.shape[0], inp.query.shape[2], inp.query.shape[1]],\n                    device=inp.query.device,\n                    dtype=torch.float32,\n                )\n\n        ctx = Context(\n            out=out,\n            lse=softmax_lse,\n        )\n\n        if not needs_gradient:\n            return out, None\n        ctx = Context(\n            out=out,\n            lse=_post_process_lse(softmax_lse, inp, tuple(original_query_shape)),\n        )\n        return (out, ctx)\n\n\n@register_operator\nclass BwOp(AttentionBwOpBase):\n    __doc__ = FwOp.__doc__\n\n    OPERATOR = get_operator(\"xformers_flash3\", \"flash_bwd\")\n    SUPPORTED_DEVICES = FwOp.SUPPORTED_DEVICES\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = FwOp.CUDA_MINIMUM_COMPUTE_CAPABILITY\n    CUDA_MAXIMUM_COMPUTE_CAPABILITY = FwOp.CUDA_MAXIMUM_COMPUTE_CAPABILITY\n    SUPPORTED_DTYPES = FwOp.SUPPORTED_DTYPES\n    SUPPORTED_MAX_K = FwOp.SUPPORTED_MAX_K\n    SUPPORTED_MIN_K = 64\n    SUPPORTED_ATTN_BIAS_TYPES = (\n        # Exclude padded or gappy masks, since seqused_k is not supported by the kernel.\n        type(None),\n        LowerTriangularMask,\n        LowerTriangularFromBottomRightMask,\n        LowerTriangularFromBottomRightLocalAttentionMask,\n        BlockDiagonalMask,\n        BlockDiagonalCausalMask,\n        BlockDiagonalCausalLocalAttentionMask,\n        BlockDiagonalCausalLocalAttentionFromBottomRightMask,\n        BlockDiagonalCausalFromBottomRightMask,\n        LocalAttentionFromBottomRightMask,\n    )\n\n    SUPPORTS_DROPOUT = FwOp.SUPPORTS_DROPOUT\n    SUPPORTS_CUSTOM_SCALE = FwOp.SUPPORTS_CUSTOM_SCALE\n    SUPPORTS_DIFFERENT_VALUE_EMBED = FwOp.SUPPORTS_DIFFERENT_VALUE_EMBED\n    IS_DETERMINISTIC = FLASH3_HAS_DETERMINISTIC_MODE\n    SUPPORTS_BMGHK = False\n    SUPPORTS_LSE_FORMATS: Sequence[str] = [\"\", \"varlen_flat\"]\n    NAME = f\"fa3B@{FLASH_VERSION}\"\n    VERSION = FLASH_VERSION\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(BwOp, cls).not_supported_reasons(d)\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        check_lastdim_alignment_stride1(reasons, \"key\", d.value, 8)\n        check_lastdim_alignment_stride1(reasons, \"value\", d.value, 8)\n        _check_needs_no_topleft(d, reasons)\n        _check_different_value_headdim_ampere(d, reasons)\n        return reasons\n\n    @classmethod\n    def apply(cls, ctx: Context, inp: Inputs, grad: torch.Tensor) -> Gradients:\n        dq_shape, dk_shape, dv_shape = inp.query.shape, inp.key.shape, inp.value.shape\n        (\n            inp,\n            cu_seqlens_q,\n            max_seqlen_q,\n            cu_seqlens_k,\n            max_seqlen_k,\n            _,  # seqused_k,\n        ) = _convert_input_format(inp, supports_mqa=False)\n        ctx_lse = ctx.lse\n\n        if isinstance(inp.attn_bias, VARLEN_BIASES):\n            assert ctx_lse.shape[0] == 1\n            ctx_lse = ctx_lse[0]\n        else:\n            # NOTE: cutlass pads the last dimension, we need to slice it\n            assert ctx_lse.shape[2] >= max_seqlen_q\n            ctx_lse = ctx_lse[:, :, :max_seqlen_q].contiguous()\n\n        kernel_out_shape = [\n            *inp.query.shape[:-1],\n            inp.value.shape[-1],\n        ]\n        assert grad.dtype in cls.SUPPORTED_DTYPES\n\n        if inp.query.numel() and inp.key.numel():\n            win_left, win_right = _window_size(inp.attn_bias)\n            dq, dk, dv = cls.OPERATOR(\n                ctx.qkv_share_storage,\n                grad.reshape(kernel_out_shape).contiguous(),\n                inp.query,\n                inp.key,\n                inp.value,\n                ctx.out.reshape(kernel_out_shape),\n                ctx.lse,\n                cu_seqlens_q,\n                cu_seqlens_k,\n                max_seqlen_q,\n                max_seqlen_k,\n                window_left=win_left,\n                window_right=win_right,\n                softmax_scale=inp.scale_float,\n                is_causal=_is_causal(inp.attn_bias),\n            )\n            grads = Gradients(dq, dk, dv)\n        else:\n            grads = Gradients(\n                dq=torch.zeros_like(inp.query),\n                dk=torch.zeros_like(inp.key),\n                dv=torch.zeros_like(inp.value),\n            )\n\n        grads.dq = grads.dq.reshape(dq_shape)\n        grads.dk = grads.dk.reshape(dk_shape)\n        grads.dv = grads.dv.reshape(dv_shape)\n        return grads\n\n\n@register_operator\nclass FwOp_KVSplit(FwOp):\n    \"\"\"Operator that computes memory-efficient attention using \\\n        `Flash-Attention3 <https://github.com/Dao-AILab/flash-attention/tree/main/hopper>`_ \\\n        implementation with heuristic rules to dispatch decoding shapes to KVSplit Attention \\\n    \"\"\"\n\n    NAME = f\"fa3F_splitKV@{FLASH_VERSION}\"\n    enable_kvsplit_attn: bool = True\n\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalPaddedKeysMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalLocalAttentionPaddedKeysMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    ) + (\n        (\n            PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n            PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n            PagedBlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalPaddedKeysMask,\n        )\n        if FLASH3_HAS_PAGED_ATTENTION\n        else tuple()\n    )\n\n    @classmethod\n    def apply(  # type: ignore[override]\n        cls,\n        inp: Inputs,\n        needs_gradient: bool,\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        use_kvsplit = _heuristic_kvsplit(inp, cls.enable_kvsplit_attn)\n\n        return super().apply(inp, needs_gradient, use_kvsplit)\n"
  },
  {
    "path": "xformers/ops/fmha/merge_training.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nfrom typing import Callable, Optional, Tuple, Type, Union\n\nimport torch\n\nfrom xformers.ops import fmha\n\n\"\"\"\nFriendly wrapper around merge_attentions which works with autograd.\n\nUse as follows\n\n```\npartial1 = memory_efficient_attention_partial_autograd(q, k1, v1, ...)\npartial2 = memory_efficient_attention_partial_autograd(q, k2, v2, ...)\nattn_out = merge_attentions_autograd(partial1, partial2)\n```\n\nmerge_attentions_autograd() can take any number of inputs. Note that\npartial1 and partial2 are not tensors, but rather objects of type\n`Partial`.\n\nIf you have partial1 and you changed your mind and don't\nwant to merge it with anything, you can do\n```\nattn_out = merge_attentions_autograd(partial1)\n```\n\n\"\"\"\n\n\nclass _PartialFunc(torch.autograd.Function):\n    @staticmethod\n    def forward(\n        ctx: torch.autograd.function.FunctionCtx,\n        query: torch.Tensor,\n        key: torch.Tensor,\n        value: torch.Tensor,\n        attn_bias: Optional[Union[torch.Tensor, fmha.AttentionBias]],\n        p: float = 0.0,\n        scale: Optional[float] = None,\n        op: Optional[Union[fmha.AttentionOp, Type[fmha.AttentionFwOpBase]]] = None,\n        output_dtype: Optional[torch.dtype] = None,\n    ) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        ctx.bias = attn_bias  # type: ignore\n        ctx.save_for_backward(query, key, value)\n        ctx.p = p  # type: ignore\n        ctx.scale = scale  # type: ignore\n        ctx.op = op[1] if isinstance(op, tuple) else None  # type: ignore\n        attn, lse = fmha.memory_efficient_attention_partial(\n            query, key, value, attn_bias, p, scale, op=op, output_dtype=output_dtype\n        )\n        placeholder = torch.empty_like(attn)\n        return attn, lse, placeholder\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx: torch.autograd.function.FunctionCtx,\n        grad_attn: torch.Tensor,\n        lse: torch.Tensor,\n        out: torch.Tensor,\n    ) -> Tuple[Optional[torch.Tensor], ...]:\n        query, key, value = ctx.saved_tensors  # type: ignore\n        grad_q, grad_k, grad_v = fmha.memory_efficient_attention_backward(\n            grad_attn,\n            out,\n            lse.contiguous(),\n            query,\n            key,\n            value,\n            ctx.bias,  # type: ignore\n            ctx.p,  # type: ignore\n            ctx.scale,  # type: ignore\n            op=ctx.op,  # type: ignore\n        )\n        return grad_q, grad_k, grad_v, None, None, None, None, None\n\n\nclass _MergeFunc(torch.autograd.Function):\n    @staticmethod\n    def forward(\n        ctx: torch.autograd.function.FunctionCtx,\n        *inputs: torch.Tensor,\n    ) -> torch.Tensor:\n        ctx.length = len(inputs) // 3  # type: ignore\n        if ctx.length > 1:  # type: ignore\n            attns = inputs[::3]\n            lses = inputs[1::3]\n            out, lse = fmha.merge_attentions(attns, lses)\n            assert lse is not None\n        else:\n            out, lse = inputs[:2]\n        ctx.save_for_backward(out, lse)\n        return out\n\n    @staticmethod\n    def backward(  # type: ignore[override]\n        ctx: torch.autograd.function.FunctionCtx, grad_out: torch.Tensor\n    ) -> Tuple[Optional[torch.Tensor], ...]:\n        out, lse = ctx.saved_tensors  # type: ignore\n        return (grad_out, lse, out) * ctx.length  # type: ignore\n\n\nclass Partial:\n    \"\"\"\n    This class is used to represent a partial attention output, which is\n    returned by `memory_efficient_attention_partial_autograd`.\n\n    Attributes: (Do not access them directly, use the methods instead.)\n\n        _attn: torch.Tensor\n        _lse: torch.Tensor . (Its grad is the full LSE to be used by\n            the individual backward passes.)\n        _placeholder: torch.Tensor, whose grad is used for passing the full attention\n            output to the individual backward passes.\n    \"\"\"\n\n    def __init__(\n        self,\n        attn: torch.Tensor,\n        lse: torch.Tensor,\n        placeholder: torch.Tensor,\n    ) -> None:\n        \"\"\"\n        Internal use only\n        \"\"\"\n        self._attn = attn\n        self._lse = lse\n        self._placeholder = placeholder\n\n    def is_bmghk(self) -> bool:\n        return self._attn.ndim == 5\n\n    def apply(self, fn: Callable[[torch.Tensor], torch.Tensor]) -> \"Partial\":\n        \"\"\"\n        Applies fn to the attention output, as if we were a tensor.\n        fn must expect tensors of shape BMGHK or BMHK, but cannot actually\n        manipulate the K dimension (because the LSE doesn't have one).\n\n        For example, to slice on the sequence dimension, you might apply\n        `lambda x: x[:, start:end]`.\n        \"\"\"\n        attn = fn(self._attn)\n        if self.is_bmghk():\n            rearranged = self._lse.permute(0, 3, 1, 2).unsqueeze(-1)\n            lse = fn(rearranged).squeeze(-1).permute(0, 2, 3, 1)\n        else:\n            rearranged = self._lse.permute(0, 2, 1).unsqueeze(-1)\n            lse = fn(rearranged).squeeze(-1).permute(0, 2, 1)\n        placeholder = fn(self._placeholder)\n        return self.__class__(attn, lse, placeholder)\n\n    def _tuple(self) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:\n        return self._attn, self._lse, self._placeholder\n\n\ndef memory_efficient_attention_partial_autograd(\n    query: torch.Tensor,\n    key: torch.Tensor,\n    value: torch.Tensor,\n    attn_bias: Optional[Union[torch.Tensor, fmha.AttentionBias]] = None,\n    p: float = 0.0,\n    scale: Optional[float] = None,\n    *,\n    op: Optional[Union[fmha.AttentionOp, Type[fmha.AttentionFwOpBase]]] = None,\n    output_dtype: Optional[torch.dtype] = None,\n) -> Partial:\n    \"\"\"\n    Wrapper around `memory_efficient_attention_partial` which works with autograd.\n    Arguments are the same as for `memory_efficient_attention_partial`.\n    \"\"\"\n    return Partial(\n        *_PartialFunc.apply(query, key, value, attn_bias, p, scale, op, output_dtype)\n    )\n\n\ndef merge_attentions_autograd(\n    *partials: Partial,\n) -> torch.Tensor:\n    \"\"\"\n    Wrapper around merge_attentions which works with autograd.\n    \"\"\"\n    args = [i for part in partials for i in part._tuple()]\n    if len(args) == 0:\n        raise ValueError(\"No partials to merge\")\n    return _MergeFunc.apply(*args)\n"
  },
  {
    "path": "xformers/ops/fmha/torch_attention_compat.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport torch\nfrom torch._C import parse_schema\n\n\ndef is_pt_cutlass_compatible(force: bool = False) -> bool:\n    if torch.version.hip is not None:\n        if force:\n            raise ImportError(\"CUTLASS is not supported on ROCm\")\n        return False\n    compatible = True\n\n    fwd_schema_str = (\n        \"aten::_efficient_attention_forward(Tensor query, Tensor key, Tensor value, \"\n        \"Tensor? bias, Tensor? cu_seqlens_q, Tensor? cu_seqlens_k, SymInt? max_seqlen_q, \"\n        \"SymInt? max_seqlen_k, float dropout_p, int custom_mask_type, bool compute_log_sumexp=False, *, \"\n        \"float? scale=None, Tensor? seqlen_k=None, int? window_size=None) -> \"\n        \"(Tensor output, Tensor logsumexp, Tensor philox_seed, Tensor philox_offset, \"\n        \"SymInt max_seqlen_batch_q, SymInt max_seqlen_batch_k)\"\n    )\n    expected_fwd_schema = parse_schema(fwd_schema_str)\n\n    current_schema = torch.ops.aten._efficient_attention_forward.default._schema\n    if not current_schema.is_backward_compatible_with(expected_fwd_schema):  # type: ignore\n        compatible = False\n\n        if force:\n            raise ImportError(\n                f\"Current Torch CUTLASS doesnt have a compatible aten::_efficient_attention_forward schema\\n\"\n                f\"EXPECTED:\\n{expected_fwd_schema}\\n\"\n                f\"but GOT:\\n{current_schema}\"\n            )\n\n    bwd_schema_str = (\n        \"aten::_efficient_attention_backward(Tensor grad_out_, Tensor query, Tensor key, Tensor value, \"\n        \"Tensor? bias, Tensor out, Tensor? cu_seqlens_q, Tensor? cu_seqlens_k, SymInt max_seqlen_q, \"\n        \"SymInt max_seqlen_k, Tensor logsumexp, float dropout_p, Tensor philox_seed, Tensor philox_offset, \"\n        \"int custom_mask_type, bool bias_requires_grad, *, float? scale=None, int? num_splits_key=None, \"\n        \"int? window_size=None, bool shared_storage_dqdkdv=False) -> (Tensor, Tensor, Tensor, Tensor)\"\n    )\n\n    expected_bwd_schema = parse_schema(bwd_schema_str)\n\n    current_schema = torch.ops.aten._efficient_attention_backward.default._schema\n    if not current_schema.is_backward_compatible_with(expected_bwd_schema):  # type: ignore\n        compatible = False\n\n        if force:\n            raise ImportError(\n                f\"Current Torch CUTLASS doesnt have a compatible aten::_efficient_attention_backward schema\\n\"\n                f\"EXPECTED:\\n{expected_bwd_schema}\\n\"\n                f\"but GOT:\\n{current_schema}\"\n            )\n\n    return compatible\n\n\ndef ensure_pt_flash_ok() -> None:\n    \"\"\"\n    Raises an ImportError if the current PyTorch version has an unexpected Flash-Attention.\n    \"\"\"\n    if not torch.backends.cuda.is_flash_attention_available():\n        raise ImportError(\"Flash SDP backend is disabled\")\n\n    if not hasattr(torch.nn, \"attention\") or not hasattr(\n        torch.nn.attention, \"_get_flash_version\"\n    ):\n        raise ImportError(\n            f\"Current Torch {torch.__version__} doesnt implement \"\n            \"torch.nn.attention._get_flash_version()\"\n        )\n\n    FLASH_VERSION = torch.nn.attention._get_flash_version()\n\n    fwd_schema_str = (\n        \"aten::_flash_attention_forward(Tensor query, Tensor key, Tensor value, \"\n        \"Tensor? cum_seq_q, Tensor? cum_seq_k, SymInt max_q, SymInt max_k, float dropout_p, \"\n        \"bool is_causal, bool return_debug_mask, *, float? scale=None, \"\n        \"SymInt? window_size_left=None, SymInt? window_size_right=None, \"\n        \"Tensor? seqused_k=None, Tensor? alibi_slopes=None) -> (Tensor output, Tensor softmax_logsumexp, \"\n        \"Tensor rng_state, Tensor unused, Tensor debug_attn_mask)\"\n    )\n    expected_fwd_schema = parse_schema(fwd_schema_str)\n\n    current_schema = torch.ops.aten._flash_attention_forward.default._schema\n    if not current_schema.is_backward_compatible_with(expected_fwd_schema):  # type: ignore\n        raise ImportError(\n            f\"Current Torch with Flash-Attention {FLASH_VERSION} doesnt have \"\n            \"a compatible aten::_flash_attention_forward schema\\n\"\n            f\"EXPECTED:\\n{expected_fwd_schema}\\n\"\n            f\"but GOT:\\n{current_schema}\"\n        )\n\n    bwd_schema_str = (\n        \"aten::_flash_attention_backward(Tensor grad_out, Tensor query, Tensor key, Tensor value, \"\n        \"Tensor out, Tensor logsumexp, Tensor cum_seq_q, Tensor cum_seq_k, SymInt max_q, SymInt max_k, \"\n        \"float dropout_p, bool is_causal, Tensor rng_state, Tensor unused, *, float? scale=None, \"\n        \"SymInt? window_size_left=None, SymInt? window_size_right=None) -> (Tensor, Tensor, Tensor)\"\n    )\n    expected_bwd_schema = parse_schema(bwd_schema_str)\n\n    current_schema = torch.ops.aten._flash_attention_backward.default._schema\n    if not current_schema.is_backward_compatible_with(expected_bwd_schema):  # type: ignore\n        raise ImportError(\n            f\"Current Torch with Flash-Attention {FLASH_VERSION} doesnt have \"\n            \"a compatible aten::_flash_attention_backward schema\\n\"\n            f\"EXPECTED:\\n{expected_bwd_schema}\\n\"\n            f\"but GOT:\\n{current_schema}\"\n        )\n"
  },
  {
    "path": "xformers/ops/fmha/triton_splitk.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport functools\nimport sys\nfrom dataclasses import dataclass\nfrom typing import (\n    Any,\n    cast,\n    Dict,\n    Iterable,\n    List,\n    Optional,\n    Sequence,\n    Tuple,\n    Type,\n    TYPE_CHECKING,\n    Union,\n)\n\nimport torch\n\nfrom ... import _is_triton_available\nfrom ..common import register_operator\nfrom .attn_bias import (\n    BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n    BlockDiagonalCausalWithOffsetGappyKeysMask,\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    BlockDiagonalGappyKeysMask,\n    BlockDiagonalLocalAttentionPaddedKeysMask,\n    BlockDiagonalPaddedKeysMask,\n    PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n    PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n)\nfrom .common import AttentionFwOpBase, check_lastdim_alignment_stride1, Context, Inputs\n\n\ndef _strides(x: Optional[torch.Tensor], *stride_names: str):\n    if x is None:\n        return {f\"stride_{name}\": None for name in stride_names}\n    assert x.ndim == len(stride_names)\n    return {f\"stride_{name}\": s for name, s in zip(stride_names, x.stride())}\n\n\ndef _is_supported_causal_bias(attn_bias: Any) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            BlockDiagonalCausalWithOffsetPaddedKeysMask,\n            BlockDiagonalCausalWithOffsetGappyKeysMask,\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n            PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n        ),\n    )\n\n\ndef _is_supported_local_bias(attn_bias: Any) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n            BlockDiagonalLocalAttentionPaddedKeysMask,\n        ),\n    )\n\n\ndef _is_supported_gappy_bias(attn_bias: Any) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            BlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalGappyKeysMask,\n        ),\n    )\n\n\ndef _is_supported_paged_bias(attn_bias: Any) -> bool:\n    return isinstance(\n        attn_bias,\n        (\n            PagedBlockDiagonalGappyKeysMask,\n            PagedBlockDiagonalPaddedKeysMask,\n        ),\n    )\n\n\n@dataclass\nclass InputsFp8(Inputs):\n    \"\"\"\n    Each of k/v_fp8_scales is an int32 tensor of shape (1, B * Mkv, Hq),\n    or (1, page_size * max_pages_per_lane, Hq) in the paged case.\n    Each int32 element contains two packed fp16 number\n    - scales and shifts for row-wise FP8 quantization.\n    \"\"\"\n\n    k_fp8_scale_shift: Optional[torch.Tensor] = None\n    v_fp8_scale_shift: Optional[torch.Tensor] = None\n\n    @property\n    def nbytes(self) -> int:\n        \"\"\"\n        Number of bytes in the input, not counting the attention bias.\n        \"\"\"\n        return (\n            super(InputsFp8, self).nbytes\n            + (\n                self.k_fp8_scale_shift.untyped_storage().nbytes()\n                if self.k_fp8_scale_shift is not None\n                else 0\n            )\n            + (\n                self.v_fp8_scale_shift.untyped_storage().nbytes()\n                if self.v_fp8_scale_shift is not None\n                else 0\n            )\n        )\n\n\nif TYPE_CHECKING or _is_triton_available():\n    from ._triton.splitk_kernels import _fwd_kernel_splitK, _splitK_reduce\nelse:\n    _fwd_kernel_splitK = None\n    _splitK_reduce = None\n\n\ndef _is_cuda() -> bool:\n    return torch.version.cuda is not None\n\n\ndef _is_cuda_at_least_sm80(device: torch.device) -> bool:\n    return _is_cuda() and torch.cuda.get_device_capability(device) >= (\n        8,\n        0,\n    )\n\n\n@register_operator\nclass FwOp(AttentionFwOpBase):\n    \"\"\"Flash-Attention with Split-K. Supports fused int4 and fp8 K/V quantization.\n    Quantized path will be taken if input K/V have type int32.\n\n    Int4 quantization can be row-wise or group-wise (when cls.NUM_GROUPS > 1) along\n    the last dimension of K and V. Currently 1, 2, 4, or 8 groups per row are supported.\n    Quantization coefficients (scale and shift) are represented as two\n    float16 constants per group, packed into int32. Quantization coefficients of\n    all groups are placed at the beginning of the row. So, if unquantized K/V have head\n    dimension D, the quantized versions have head dimension D // 8 + NUM_GROUPS\n    and dtype int32.\n    Pseudocode for dequantizing one row can look like:\n    group_size = D // 8\n    for i in range(NUM_GROUPS):\n        group_start = NUM_GROUPS + i * group_size\n        group_quant = K[..., group_start: group_start + group_size]\n        scale, shift = unpack_int32_into_float16x2(group_quant[0])\n        group_dequant = group_quant[..., 1:] * scale + shift\n    ...\n\n    For fp8 only row-wise quantization is supported. To use it, provide input of type\n    xformers.ops.fmha.triton_splitk.InputsFp8 (instead of the usual xformers.ops.fmha.Inputs) to\n    xformers.ops.fmha.triton_splitk.FwOp.apply or xformers.ops.fmha._memory_efficient_attention_forward.\n\n    This op uses Paged Attention when bias is one of the Paged* classes.\n    In this case bias has additional fields:\n    - block_tables of shape [batch_size, max_num_pages]\n    - K/V of shape [1, max_num_pages * page_size, num_heads, head_dim]\n      or [1, max_num_pages * page_size, num_groups, num_heads, head_dim]\n\n    The shape which the kernel takes the queries and the output\n    is quite different from the user interface. There are three\n    types of input (a) no bias / tensor bias, (b) variable q_len\n    (which is only for non causal) and (c) other bias objects.\n    From the interface to the kernel the following changes happen.\n\n    (0) In all cases, a group dimension may need to be added.\n\n    (1) For (c), a batch dimension is created, reshaping from (1, B*Mq, G, Hq, K)\n        to (B, Mq, G, Hq, K)\n\n    (2) For (a) and (c), in the case of multiquery (i.e. the head dimension\n        of keys and values is expanded), the head-swapping trick\n        reshaping from (B, Mq, G, Hq, K) to (B, M=Hq*Mq, G, H=1, K)\n\n    (3) For (b), in the case of multiquery, the head-swapping trick\n        trick, reshaping from (1, Mq, G, Hq, K) to (1, Mq*Hq, G, H=1, K)\n        Note here that Mq is a single long dimension which spans all the queries\n        in the batch, unlike in case (C). Also that Hq has to run faster than\n        Mq in order that the queries in a batch element remain evenly spaced.\n\n    In all cases, the shape as seen by the kernel is called (Bqq, Mqq, G, H, K).\n    The kernel operates on B batch elements and M queries per batch element.\n    \"\"\"\n\n    OPERATOR = True\n    SUPPORTED_DEVICES = {\"cuda\"}\n    CUDA_MINIMUM_COMPUTE_CAPABILITY = (8, 0)\n    SUPPORTED_DTYPES = {\n        torch.half,\n        torch.bfloat16,\n    }  # Those are dtypes of Q. In the quantized case K/V has dtype int32\n    SUPPORTED_MAX_K = 512\n    SUPPORTED_ATTN_BIAS_TYPES: Iterable[Any] = (\n        type(None),\n        torch.Tensor,\n        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n        BlockDiagonalLocalAttentionPaddedKeysMask,\n        BlockDiagonalGappyKeysMask,\n        BlockDiagonalCausalWithOffsetGappyKeysMask,\n        BlockDiagonalPaddedKeysMask,\n        PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n        PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n        PagedBlockDiagonalGappyKeysMask,\n        PagedBlockDiagonalPaddedKeysMask,\n    )\n    SUPPORTS_DROPOUT = False\n    SUPPORTS_CUSTOM_SCALE = True\n    SUPPORTS_BMGHK = True\n    SUPPORTS_OUTPUT_DTYPE = True\n    SUPPORTS_PARTIAL = True\n    NAME = \"triton_splitKF\"\n\n    SPLIT_K: Optional[int] = None\n    MAX_BLOCK_M = 32\n\n    # Whether blocks attending to no part of a variable sequence length\n    # should exit early. This requires extra kernels to run beforehand\n    # to initialise the outputs.\n    # TODO: avoid these by making the reduce kernel work out it doesn't need\n    # to look at the irrelevant places.\n    SPLIT_K_EARLY_EXIT: bool = False\n\n    # Perform kernel-level Triton autotune\n    AUTOTUNE = False\n\n    NUM_GROUPS = 1  # Default quantization is row-wise\n    NUM_GROUPS_VALUES = [1, 2, 4, 8]\n\n    # Values below are used when autotune=False.\n    # Note that under certain conditions different values might be used, see the code just before the kernel launch.\n    BLOCK_M: int = 16  # When M > 1, different BLOCK_M can be used.\n    BLOCK_N: int = 64\n    # On AMD or for M > 1 different NUM_STAGES and NUM_WARPS can be used.\n    NUM_STAGES: int = 1\n    NUM_WARPS: int = 2\n\n    @classmethod\n    def shape_not_supported_reasons(\n        cls, Mq: int, Mkv: int, K: int, Kv: int\n    ) -> List[str]:\n        reasons = super().shape_not_supported_reasons(Mq, Mkv, K, Kv)\n        if K not in {16, 32, 64, 128, 256, 512}:\n            reasons.append(f\"Embed dim {K} not supported\")\n        return reasons\n\n    @classmethod\n    def not_supported_reasons(cls, d: Inputs) -> List[str]:\n        reasons = super(FwOp, cls).not_supported_reasons(d)\n        if (sys.version_info.major, sys.version_info.minor) < (3, 9):\n            reasons.append(\"triton_splitk requires python 3.9 or above!\")\n        check_lastdim_alignment_stride1(reasons, \"query\", d.query, 8)\n        if d.key.dtype != torch.int32:\n            check_lastdim_alignment_stride1(reasons, \"key\", d.key, 8)\n            check_lastdim_alignment_stride1(reasons, \"value\", d.value, 8)\n        if cls.OPERATOR is None:\n            reasons.append(\"triton is not available\")\n        if d.device.type == \"cuda\":\n            # Has only been tested on 8.0 / 9.0.\n            if _is_cuda() and not _is_cuda_at_least_sm80(d.device):\n                reasons.append(\n                    \"requires NVidia GPU with sm80 minimum compute capacity, e.g., A100/H100/L4\"\n                )\n            # TODO: AMD GPU support matrix needs to be figured out. MI300X is tested to work.\n\n        q_len = d.query.shape[1]\n        is_block_diagonal = isinstance(\n            d.attn_bias, (BlockDiagonalPaddedKeysMask, BlockDiagonalGappyKeysMask)\n        )\n        is_paged = _is_supported_paged_bias(d.attn_bias)\n        is_causal = _is_supported_causal_bias(d.attn_bias)\n        is_local = _is_supported_local_bias(d.attn_bias)\n        if is_block_diagonal or is_paged:\n            seqinfo = d.attn_bias.q_seqinfo  # type: ignore\n            if q_len != seqinfo.seqstart_py[-1]:\n                reasons.append(\n                    f\"Expected total {seqinfo.seqstart_py[-1]} queries not {q_len}\"\n                )\n            q_len = seqinfo.max_seqlen\n            if is_causal and q_len != seqinfo.min_seqlen:\n                reasons.append(\n                    f\"Variable query len is not supported for causal masks: got {seqinfo.max_seqlen=} {seqinfo.min_seqlen=}.\"\n                )\n            elif is_local and q_len != seqinfo.min_seqlen:\n                reasons.append(\n                    f\"Variable query len is not supported for local masks: got {seqinfo.max_seqlen=} {seqinfo.min_seqlen=}.\"\n                )\n        if q_len > 16 and (is_causal or is_local):\n            # 16 is the minimum BLOCK_M which gets used\n            # XXX I don't really understand why this is needed.\n            reasons.append(\n                \"Query length should not be larger than 16 for causal or local attention biases\"\n            )\n\n        if is_paged:\n            page_size = d.attn_bias.page_size  # type: ignore\n            if d.key.shape[1] % page_size:\n                reasons.append(\n                    \"For paged attention, key.shape[1] should be divisible \"\n                    \"by the page size, \"\n                    f\"but got {d.key.shape[1]=}, {page_size=}.\"\n                )\n            if page_size % cls.BLOCK_N:\n                reasons.append(\n                    \"For paged attention, page size should be divisible \"\n                    \"by the block size, \"\n                    f\"but got {page_size=}, {cls.BLOCK_N=}.\"\n                )\n\n        if isinstance(d.attn_bias, torch.Tensor):\n            if d.attn_bias.ndim not in (4, 5):\n                reasons.append(\n                    \"Additive attention bias has to have shape (B, G, H, Mq, Mkv) \"\n                    f\"or (B, H, Mq, Mkv), but got {d.attn_bias.shape}.\"\n                )\n            if cls.SPLIT_K is not None and cls.SPLIT_K > 1:\n                reasons.append(\n                    \"Additive attention bias is not supported with split-k > 1.\"\n                )\n\n        return reasons\n\n    @classmethod\n    def get_split_k(\n        cls, B: int, G: int, H: int, Mk: int, Mq: int, page_size: int, is_paged=False\n    ) -> int:\n        \"\"\"Heuristic for the number of splits\"\"\"\n        bh = max(B * H, 1)  # NOTE: Handle B*h=0 case\n        if torch.version.hip:\n            split_k = max(Mk + bh - 1, 1024) // bh\n            max_chunk_size = 64\n            split_k_stop_val = max(1024 / (B * G * H), 1)\n            while split_k > 1 and Mk / (split_k - 1) < max_chunk_size:\n                split_k = split_k - 1\n\n            while split_k > split_k_stop_val:\n                split_k = split_k // 2\n\n            split_size = (Mk + split_k - 1) // max(split_k, 1)\n\n            chunk_size = split_size // max_chunk_size * max_chunk_size\n            if chunk_size < split_size:\n                split_k += 1\n\n            split_k_upper_bound = 512\n        else:\n            if Mq > 1 and B * G * H > 64:\n                return 1\n            split_k = max(Mk, 1024) // bh\n            max_chunk_size = 64 if Mk <= 512 and bh <= 64 else 128\n            split_k_stop_val = Mk / max_chunk_size\n            split_k_upper_bound = 64\n\n            while split_k > split_k_stop_val:\n                split_k = split_k // 2\n\n        split_k = min(split_k, split_k_upper_bound)\n        split_k = max(split_k, 1)\n\n        # makes no sense that split_size is larger than page_size\n        if is_paged and torch.version.hip:\n            split_size = (Mk + split_k - 1) // split_k\n            if split_size > page_size:\n                split_size = page_size\n                split_k = (Mk + split_size - 1) // split_size\n\n        return split_k\n\n    @classmethod\n    def get_kernel(cls):\n        from ._triton.splitk_kernels import (\n            _fwd_kernel_splitK_autotune,\n            _get_splitk_kernel,\n        )\n\n        if cls.AUTOTUNE:\n            return _fwd_kernel_splitK_autotune[cls.NUM_GROUPS]\n        else:\n            return _get_splitk_kernel(cls.NUM_GROUPS)\n\n    @classmethod\n    def get_fp8_scale_shift(\n        cls, inp: Inputs\n    ) -> Tuple[Optional[torch.Tensor], Optional[torch.Tensor]]:\n        if not hasattr(inp, \"k_fp8_scale_shift\"):\n            return None, None\n        inp_ = cast(InputsFp8, inp)\n        k_fp8_scale_shift = inp_.k_fp8_scale_shift\n        v_fp8_scale_shift = inp_.v_fp8_scale_shift\n        assert k_fp8_scale_shift is not None\n        assert v_fp8_scale_shift is not None\n        if k_fp8_scale_shift.ndim == 3:\n            return k_fp8_scale_shift.unsqueeze(2), v_fp8_scale_shift.unsqueeze(2)\n        if k_fp8_scale_shift.ndim == 4:\n            return k_fp8_scale_shift, v_fp8_scale_shift\n        raise ValueError(\n            \"FP8 scales have to be provided in BMH or BMGH format, \"\n            f\"but got {k_fp8_scale_shift.shape=}\"\n        )\n\n    @classmethod\n    def get_extra_args(\n        cls,\n        *,\n        is_paged: bool,\n        B: int,\n        M: int,\n        Kkv: int,\n        Kq: int,\n        Mq: int,\n        split_k: int,\n        attn_bias: Any,\n        k_fp8_scale_shift: Any,\n    ) -> Dict[str, Any]:\n        BLOCK_M = cls.BLOCK_M\n        BLOCK_N = cls.BLOCK_N\n        if cls.AUTOTUNE:\n            extra_args = {}\n        else:\n            # TODO: remove this when autotuning on AMD is working\n            num_warps = cls.NUM_WARPS\n            num_stages = cls.NUM_STAGES\n            if torch.version.hip and attn_bias is not None:\n                # TODO: Double check paged.\n                mkv = attn_bias.k_seqinfo.max_seqlen\n                # TODO: Determine heuristics for paged attention\n                use_fp8_path = k_fp8_scale_shift is not None\n                if B == 1:\n                    if use_fp8_path:\n                        # Use specialized configs for FP8\n                        if mkv <= 256:\n                            BLOCK_N = 16\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv <= 2048:\n                            BLOCK_N = 32\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv <= 16384:\n                            BLOCK_N = 64\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv >= 131072:\n                            BLOCK_N = 128\n                            num_warps = 2\n                            num_stages = 1\n                        else:\n                            # Note: We don't have data for when transitioning num_wraps works well\n                            BLOCK_N = 64\n                            num_warps = 4\n                            num_stages = 1\n                    else:\n                        num_warps = 4\n                        num_stages = 1  # TODO num_stages = 0 gives better perf on AMD, but sometimes produces NaNs\n                        BLOCK_N = 32\n                elif B <= 4 and split_k <= 128:\n                    num_warps = 2\n                    num_stages = 1\n                    BLOCK_N = 32\n                elif B <= 16:\n                    if use_fp8_path:\n                        if mkv <= 256:\n                            BLOCK_N = 16\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv <= 4096:\n                            BLOCK_N = 32\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv <= 8192:\n                            BLOCK_N = 16\n                            num_warps = 2\n                            num_stages = 1\n                        elif mkv < 131072:\n                            # Note: This isn't benchmarked, but fp8 seems to scale well.\n                            BLOCK_N = 64\n                            num_warps = 1\n                            num_stages = 1\n                        else:\n                            BLOCK_N = 128\n                            num_warps = 1\n                            num_stages = 1\n                    else:\n                        if M < 16:\n                            num_warps = 2\n                            num_stages = 1\n                        else:\n                            num_warps = 1\n                            num_stages = 1\n                        BLOCK_N = 32\n                elif B <= 64 and use_fp8_path:\n                    if is_paged:\n                        num_stages = 1\n                        if mkv <= 256:\n                            BLOCK_N = 64\n                            num_warps = 8\n                        elif mkv <= 8192:\n                            BLOCK_N = 64\n                            num_warps = 1\n                        elif mkv <= 16384:\n                            BLOCK_N = 128\n                            num_warps = 2\n                        else:\n                            # Note: This isn't benchmarked, but fp8 seems to scale well.\n                            BLOCK_N = 128\n                            num_warps = 1\n                    else:\n                        if mkv <= 256:\n                            BLOCK_N = 16\n                            num_warps = 4\n                            num_stages = 1\n                        elif mkv < 131072:\n                            # Note: This isn't benchmarked, but fp8 seems to scale well.\n                            BLOCK_N = 64\n                            num_warps = 1\n                            num_stages = 1\n                        else:\n                            BLOCK_N = 128\n                            num_warps = 1\n                            num_stages = 1\n                elif B <= 128 and use_fp8_path:\n                    num_stages = 1\n                    if is_paged:\n                        if mkv <= 256:\n                            num_warps = 4\n                            BLOCK_N = 16\n                        elif mkv <= 2048:\n                            num_warps = 1\n                            BLOCK_N = 64\n                        elif mkv < 131072:\n                            num_warps = 2\n                            BLOCK_N = 128\n                        else:\n                            # Note: This isn't benchmarked, but fp8 seems to scale well.\n                            num_warps = 1\n                            BLOCK_N = 128\n                    else:\n                        if mkv <= 128:\n                            num_warps = 4\n                            BLOCK_N = 16\n                        else:\n                            num_warps = 1\n                            BLOCK_N = 64\n                elif B <= 256 and use_fp8_path:\n                    num_stages = 1\n                    if is_paged:\n                        if mkv <= 2048:\n                            num_warps = 1\n                            BLOCK_N = 64\n                        elif mkv < 131072:\n                            num_warps = 2\n                            BLOCK_N = 128\n                        else:\n                            # Note: This isn't benchmarked, but fp8 seems to scale well.\n                            num_warps = 1\n                            BLOCK_N = 128\n                    else:\n                        if mkv <= 256:\n                            num_warps = 2\n                            BLOCK_N = 32\n                        else:\n                            num_warps = 1\n                            BLOCK_N = 64\n                else:\n                    num_warps = 1\n                    num_stages = 1\n                    BLOCK_N = 64\n            else:\n                should_modify_warp_and_block = (\n                    Kkv == 128\n                    and Kq == 128\n                    and torch.cuda.get_device_capability() >= (8, 9)\n                )\n                if should_modify_warp_and_block:\n                    if Mq > 1:\n                        num_warps = 4\n                    # Choose minimal round block size which covers M.\n                    if M > 16:\n                        BLOCK_M = 32\n                    if M > 32:\n                        BLOCK_M = 64\n                    if M > 64:\n                        BLOCK_M = 128\n            extra_args = {\n                \"BLOCK_M\": BLOCK_M,\n                \"BLOCK_N\": BLOCK_N,\n                \"num_warps\": num_warps,\n                \"num_stages\": num_stages,\n            }\n        return extra_args\n\n    @classmethod\n    def apply(\n        cls,\n        inp: Inputs,\n        needs_gradient: bool,\n    ) -> Tuple[torch.Tensor, Optional[Context]]:\n        \"\"\"\n        Note that inp can be of type InputsFp8, in which case K/V are assumed to be row-wise FP8-quantized.\n        This is different from int4 quantization, where coefficients are kept together with the quantized\n        values at the beginning of each row, and inp has type Inputs.\n        \"\"\"\n\n        output_dtype = inp.get_output_dtype()\n        # LSE may need higher precision than output\n        output_f64_lse = output_dtype in (torch.float32, torch.float64)\n        lse_dtype = torch.float64 if output_f64_lse else torch.float32\n\n        if inp.query.numel() == 0 or inp.key.numel() == 0:\n            out = torch.zeros_like(inp.query)\n            if needs_gradient:\n                lse_out = torch.full(\n                    (inp.query.shape[0],)\n                    + inp.query.shape[2:-1]\n                    + (inp.query.shape[1],),\n                    float(\"-inf\"),\n                    device=inp.query.device,\n                    dtype=lse_dtype,\n                )\n                return out, Context(out=out, lse=lse_out)\n            return out, None\n\n        k_fp8_scale_shift, v_fp8_scale_shift = cls.get_fp8_scale_shift(inp)\n\n        if not isinstance(inp.attn_bias, torch.Tensor):\n            attn_bias_tensor = None\n            attn_bias = cast(\n                Optional[\n                    Union[\n                        BlockDiagonalCausalWithOffsetPaddedKeysMask,\n                        BlockDiagonalCausalLocalAttentionPaddedKeysMask,\n                        BlockDiagonalLocalAttentionPaddedKeysMask,\n                        BlockDiagonalGappyKeysMask,\n                        BlockDiagonalCausalWithOffsetGappyKeysMask,\n                        BlockDiagonalPaddedKeysMask,\n                        PagedBlockDiagonalCausalWithOffsetPaddedKeysMask,\n                        PagedBlockDiagonalCausalWithOffsetGappyKeysMask,\n                        PagedBlockDiagonalGappyKeysMask,\n                        PagedBlockDiagonalPaddedKeysMask,\n                    ]\n                ],\n                inp.attn_bias,\n            )\n        else:\n            attn_bias_tensor = inp.attn_bias\n            attn_bias = None\n\n        seq_len = None\n        seq_starts_k = None\n        seq_starts_q = None\n        seq_starts_q_multiplier = None\n        q, k, v = inp.get_qkv_in_bmghk()\n        IS_CAUSAL = False\n        IS_LOCAL = False\n        NUM_QUERIES_CAUSAL = 1\n        variable_q = False\n        window_left = -1\n        window_right = -1\n\n        is_block_diagonal = isinstance(attn_bias, BlockDiagonalPaddedKeysMask)\n        is_gappy = _is_supported_gappy_bias(attn_bias)\n        is_paged = _is_supported_paged_bias(attn_bias)\n        if attn_bias is not None:\n            assert is_paged or is_block_diagonal or is_gappy\n            assert attn_bias.k_seqinfo.seqlen.device == inp.query.device\n            seq_len = attn_bias.k_seqinfo.seqlen\n            assert seq_len.stride(0) == 1\n            if is_gappy:\n                seq_starts_k = attn_bias.k_seqinfo.seqstart\n                assert seq_starts_k.stride(0) == 1\n            assert q.shape[0] == 1\n            B = len(seq_len)\n            G, Hq, Kq = q.shape[-3:]\n            # force a bool because triton cannot take np.bool_\n            multiple_q = bool(attn_bias.q_seqinfo.max_seqlen > 1)\n            IS_CAUSAL = multiple_q and _is_supported_causal_bias(attn_bias)\n            IS_LOCAL = _is_supported_local_bias(attn_bias)\n            variable_q = multiple_q and not IS_CAUSAL\n            Kkv = v.shape[-1]\n            if isinstance(attn_bias, BlockDiagonalLocalAttentionPaddedKeysMask):\n                window_left = attn_bias.window_left\n                window_right = attn_bias.window_right\n            elif isinstance(attn_bias, BlockDiagonalCausalLocalAttentionPaddedKeysMask):\n                window_left = attn_bias._window_size - 1\n\n            if variable_q:\n                seq_starts_q = attn_bias.q_seqinfo.seqstart\n                seq_starts_q_multiplier = 1\n                assert seq_starts_q.stride(0) == 1\n            else:\n                q = q.view(B, -1, G, Hq, Kq)\n\n            kv_shape = (1 if is_paged or is_gappy else B, -1, G, Hq, Kkv)\n            k = k.view(kv_shape)\n            v = v.view(kv_shape)\n            if k_fp8_scale_shift is not None and v_fp8_scale_shift is not None:\n                k_fp8_scale_shift = k_fp8_scale_shift.view(kv_shape[:-1])\n                v_fp8_scale_shift = v_fp8_scale_shift.view(kv_shape[:-1])\n\n            Mq = q.shape[1]\n            NUM_QUERIES_CAUSAL = Mq\n        else:\n            B, Mq, G, Hq, Kq = q.shape\n\n        if attn_bias_tensor is not None and attn_bias_tensor.ndim == 4:\n            # (B, H, Mq, Mkv) -> (B, G, H, Mq, Mkv)\n            attn_bias_tensor = attn_bias_tensor.unsqueeze(1)\n\n        # In the case of MQA/GQA, we make q have sequence length (H * Mq) and only one \"head\".\n        mqa_swap_seqlen_head = False\n        if (\n            k.shape[3] > 1\n            and k.stride(3) == 0\n            and v.stride(3) == 0\n            and attn_bias_tensor is None\n        ):\n            mqa_swap_seqlen_head = True\n            if variable_q:\n                seq_starts_q_multiplier = Hq\n                assert q.shape[0] == 1\n                # The idea is Hq,Mq are reshaped to (M=Mq*Hq, H=1)\n                q = q.permute(0, 1, 3, 2, 4).reshape(1, -1, G, 1, Kq)\n            else:\n                # This is a copy iff Mq, G and H are all > 1.\n                # The idea is Hq,Mq are reshaped to (M=Hq*Mq, H=1)\n                q = q.permute(0, 3, 1, 2, 4).reshape(q.shape[0], -1, G, 1, Kq)\n            k = k[:, :, :, :1]\n            v = v[:, :, :, :1]\n            if k_fp8_scale_shift is not None and v_fp8_scale_shift is not None:\n                k_fp8_scale_shift = k_fp8_scale_shift[:, :, :, :1]\n                v_fp8_scale_shift = v_fp8_scale_shift[:, :, :, :1]\n\n        if k.dtype == torch.int32:\n            if k_fp8_scale_shift is not None:\n                Lk = k.shape[-1] * 4\n                PACKED_PER_VAL = 4\n            else:\n                # Quantized K/V\n                PACKED_PER_VAL = 8\n                Lk = (k.shape[-1] - cls.NUM_GROUPS) * 8\n        else:\n            Lk = k.shape[-1]\n            PACKED_PER_VAL = 1\n            assert cls.NUM_GROUPS == 1, f\"{cls.NUM_GROUPS=}\"\n\n        _, Mk, G, H, Kkv = k.shape\n        Bqq, Mqq, G, H, Kq = q.shape\n        assert Lk == Kq, f\"Keys have head dim {Lk} but queries have head dim {Kq}\"\n        if variable_q:\n            assert attn_bias is not None\n            assert seq_starts_q_multiplier is not None\n            M = attn_bias.q_seqinfo.max_seqlen * seq_starts_q_multiplier\n        else:\n            M = Mqq\n        page_size = inp.attn_bias.page_size if is_paged else 0  # type: ignore\n        block_tables = None\n        kv_cache_blocks_per_row = 0\n        if is_paged:\n            block_tables = inp.attn_bias.block_tables  # type: ignore\n            kv_cache_blocks_per_row = block_tables.shape[1]\n            Mk = block_tables.shape[1] * page_size\n        elif attn_bias is not None:\n            Mk = min(Mk, attn_bias.k_seqinfo.max_seqlen)\n\n        if cls.SPLIT_K is not None:\n            split_k = cls.SPLIT_K\n        else:\n            # Use heuristics\n            split_k = (\n                cls.get_split_k(B, G, H, Mk, Mq, page_size, is_paged)\n                if attn_bias_tensor is None\n                else 1\n            )\n\n        # M_ceil = Mqq rounded up to a multiple of MAX_BLOCK_M\n        M_ceil = (Mqq + cls.MAX_BLOCK_M - 1) // cls.MAX_BLOCK_M * cls.MAX_BLOCK_M\n        IS_SPLITK = split_k > 1  # or cls.autotune?\n        output_shape = (Bqq, Mq, G, Hq, Kq)\n        if IS_SPLITK:\n            o_splitk_dtype = (\n                torch.float64 if output_dtype == torch.float64 else torch.float32\n            )\n            if cls.SPLIT_K_EARLY_EXIT:\n                o_splitk = torch.zeros(\n                    [Bqq, G, H, split_k, M_ceil, Kq],\n                    dtype=o_splitk_dtype,\n                    device=q.device,\n                )\n            else:\n                o_splitk = torch.empty(\n                    [Bqq, G, H, split_k, M_ceil, Kq],\n                    dtype=o_splitk_dtype,\n                    device=q.device,\n                )\n        else:\n            o_splitk = torch.empty(\n                [Bqq, split_k, Mqq, G, H, Kq],\n                dtype=output_dtype,\n                device=q.device,\n            ).permute(0, 3, 4, 1, 2, 5)\n        lse, lse_splitk = None, None\n        if IS_SPLITK or needs_gradient:\n            if IS_SPLITK or output_f64_lse:\n                lse_splitk_dtype = torch.float64\n            else:\n                lse_splitk_dtype = torch.float32\n            if cls.SPLIT_K_EARLY_EXIT:\n                lse_splitk = torch.full(\n                    [Bqq, G, H, split_k, Mqq],\n                    -float(\"inf\"),\n                    dtype=lse_splitk_dtype,\n                    device=q.device,\n                )\n            else:\n                lse_splitk = torch.empty(\n                    [Bqq, G, H, split_k, Mqq],\n                    dtype=lse_splitk_dtype,\n                    device=q.device,\n                )\n\n        def grid(META):\n            import triton\n\n            return triton.cdiv(M, META[\"BLOCK_M\"]), B * G * H, split_k\n\n        split_size = (Mk + split_k - 1) // split_k\n        use_seq_len = seq_len is not None\n\n        kernel = cls.get_kernel()\n        extra_args = cls.get_extra_args(\n            is_paged=is_paged,\n            B=B,\n            M=M,\n            Kkv=Kkv,\n            Kq=Kq,\n            Mq=Mq,\n            split_k=split_k,\n            attn_bias=attn_bias,\n            k_fp8_scale_shift=k_fp8_scale_shift,\n        )\n\n        if _is_triton_available():\n            # Triton 3.3.1+fb is required for AMD specific changes to\n            # improve performance.\n            # TODO: Remove once the triton update lands everywhere.\n            import triton\n\n            IS_TRITON_UPGRADE = triton.__version__ == \"3.3.1+fb\"\n        else:\n            IS_TRITON_UPGRADE = False\n        IS_HIP = IS_TRITON_UPGRADE and torch.version.hip is not None\n        kernel[grid](\n            Q=q,\n            K=k,\n            V=v,\n            sm_scale=inp.scale_float,\n            Out_splitK=o_splitk,\n            LSE_splitk=lse_splitk,\n            block_tables=block_tables,\n            Seq_len=seq_len,\n            Seq_starts_k=seq_starts_k,\n            Seq_starts_q=seq_starts_q,\n            Seq_starts_q_multiplier=seq_starts_q_multiplier,\n            additive_bias=attn_bias_tensor,\n            K_fp8_scale_shift=k_fp8_scale_shift,\n            V_fp8_scale_shift=v_fp8_scale_shift,\n            **_strides(q, \"qz\", \"qm\", \"qg\", \"qh\", \"qk\"),\n            **_strides(k, \"kz\", \"kn\", \"kg\", \"kh\", \"kk\"),\n            **_strides(v, \"vz\", \"vn\", \"vg\", \"vh\", \"vk\"),\n            **_strides(o_splitk, \"osk_z\", \"osk_g\", \"osk_h\", \"osk_s\", \"osk_m\", \"osk_k\"),\n            **_strides(lse_splitk, \"lsek_z\", \"lsek_g\", \"lsek_h\", \"lsek_s\", \"lsek_m\"),\n            **_strides(block_tables, \"blocktablesz\", \"blocktablesl\"),\n            **_strides(\n                attn_bias_tensor, \"bias_b\", \"bias_g\", \"bias_h\", \"bias_qm\", \"bias_km\"\n            ),\n            **_strides(\n                k_fp8_scale_shift,\n                \"k_fp8_scale_shift_z\",\n                \"k_fp8_scale_shift_n\",\n                \"k_fp8_scale_shift_g\",\n                \"k_fp8_scale_shift_h\",\n            ),\n            **_strides(\n                v_fp8_scale_shift,\n                \"v_fp8_scale_shift_z\",\n                \"v_fp8_scale_shift_n\",\n                \"v_fp8_scale_shift_g\",\n                \"v_fp8_scale_shift_h\",\n            ),\n            kv_cache_blocks_per_row=kv_cache_blocks_per_row,\n            Z=B,\n            H=H,\n            G=G,\n            N_CTX_Q=M,\n            N_CTX_K=Mk,\n            BLOCK_N_PER_SPLIT=split_size,\n            BLOCK_DMODEL=Lk,\n            USE_SEQ_LEN=use_seq_len,\n            PACKED_PER_VAL=PACKED_PER_VAL,\n            N_GROUPS=cls.NUM_GROUPS,\n            IS_CAUSAL=IS_CAUSAL,\n            IS_LOCAL=IS_LOCAL,\n            NUM_QUERIES_CAUSAL=NUM_QUERIES_CAUSAL,\n            IS_SPLITK=IS_SPLITK,\n            SPLIT_K_EARLY_EXIT=cls.SPLIT_K_EARLY_EXIT,\n            USE_PAGED_ATTENTION=is_paged,\n            PAGE_SIZE=page_size,\n            WINDOW_LEFT=window_left,\n            WINDOW_RIGHT=window_right,\n            WRITE_LSE=IS_SPLITK or needs_gradient,\n            HAS_ADDITIVE_BIAS=attn_bias_tensor is not None,\n            NUM_PROGRAMS_DIM2_CONST=split_k,\n            IS_HIP=IS_HIP,\n            **extra_args,\n        )\n        if not IS_SPLITK:\n            out = o_splitk[:, :, :, 0]  # Bqq, G, H, Mqq, Kq\n            if variable_q and mqa_swap_seqlen_head:\n                out = out.view(1, G, Mq, Hq, Kq).permute(0, 2, 1, 3, 4).contiguous()\n            else:\n                out = out.view(Bqq, G, Hq, Mq, Kq)\n                # This is a copy iff mqa_swap_seqlen_head and Mq, G and Hq are all > 1.\n                out = out.permute(0, 3, 1, 2, 4).contiguous()\n            if needs_gradient:\n                assert lse_splitk is not None\n                lse = lse_splitk[:, :, :, 0]  # Bqq, G, H, Mqq\n                if variable_q and mqa_swap_seqlen_head:\n                    lse = lse.view(1, G, Mq, Hq).permute(0, 1, 3, 2)\n                else:\n                    lse = lse.view(Bqq, G, Hq, Mq)\n                    if attn_bias is not None and not variable_q:\n                        lse = lse.permute(1, 2, 0, 3).reshape(1, G, Hq, B * Mq)\n            else:\n                lse = None\n\n            if inp.query.ndim == 4:\n                # BMGHK -> BMHK\n                assert G == 1\n                if lse is not None:\n                    lse = lse[:, 0]\n                out = out[:, :, 0]\n\n            if lse is None:\n                return out, None\n            return out, Context(out=out, lse=lse)\n\n        out = torch.empty(output_shape, device=q.device, dtype=output_dtype)\n\n        # Merge attention and LSE outputs from different split-k chunks\n        assert lse_splitk is not None\n        output_lse = None\n        if needs_gradient:\n            if attn_bias is None or variable_q:\n                output_lse = torch.empty(\n                    (Bqq, G, Hq, Mq), device=q.device, dtype=lse_dtype\n                )\n                lse = output_lse\n            else:\n                output_lse = torch.empty(\n                    (1, G, Hq, B * Mq), device=q.device, dtype=lse_dtype\n                )\n                lse = output_lse.view(G, Hq, B, Mq).permute(2, 0, 1, 3)\n\n        o_splitk = o_splitk[:, :, :, :, :Mqq]\n\n        if mqa_swap_seqlen_head:\n            if variable_q:\n                o_splitk = o_splitk.view(Bqq, G, split_k, Mq, Hq, Kq).permute(\n                    0, 1, 4, 2, 3, 5\n                )\n                lse_splitk = lse_splitk.view(Bqq, G, split_k, Mq, Hq).permute(\n                    0, 1, 4, 2, 3\n                )\n            else:\n                o_splitk = o_splitk.view(Bqq, G, split_k, Hq, Mq, Kq).permute(\n                    0, 1, 3, 2, 4, 5\n                )\n                lse_splitk = lse_splitk.view(Bqq, G, split_k, Hq, Mq).permute(\n                    0, 1, 3, 2, 4\n                )\n\n        merge_attentions(out, lse, o_splitk, lse_splitk)\n\n        if inp.query.ndim == 4:\n            # BMGHK -> BMHK\n            assert G == 1\n            out = out[:, :, 0]\n            if output_lse is not None:\n                output_lse = output_lse[:, 0]\n        if Mk == 0:\n            out.zero_()\n\n        if attn_bias is not None and not variable_q:\n            out = out.view(1, B * Mq, G, Hq, Kq)\n\n        if output_lse is None:\n            return out, None\n\n        return out, Context(out=out, lse=output_lse)\n\n    @classmethod\n    @functools.lru_cache\n    def get_operator(\n        cls,\n        splitk: int,\n        *,\n        block_m: Optional[int] = None,\n        block_n: Optional[int] = None,\n        num_warps: Optional[int] = None,\n        num_stages: Optional[int] = None,\n        split_k_early_exit: Optional[bool] = None,\n    ) -> Type[AttentionFwOpBase]:\n        kwargs = {\n            \"NAME\": f\"triton_splitK{splitk}\",\n            \"SPLIT_K\": splitk,\n        }\n        if block_m is not None:\n            kwargs[\"BLOCK_M\"] = block_m\n        if block_n is not None:\n            kwargs[\"BLOCK_N\"] = block_n\n        if num_warps is not None:\n            kwargs[\"NUM_WARPS\"] = num_warps\n        if num_stages is not None:\n            kwargs[\"NUM_STAGES\"] = num_stages\n        if split_k_early_exit is not None:\n            kwargs[\"SPLIT_K_EARLY_EXIT\"] = split_k_early_exit\n        return type(\n            f\"FwOp_S{splitk}\",\n            (cls,),\n            kwargs,\n        )\n\n\ndef merge_attentions(\n    attn_out: torch.Tensor,\n    lse_out: Optional[torch.Tensor],\n    attn_split: torch.Tensor,\n    lse_split: torch.Tensor,\n):\n    import triton\n\n    from ._triton.splitk_kernels import _splitK_reduce\n\n    B, M, G, H, Kq = attn_out.shape\n    B1, G1, H1, split_k, M1, Kq1 = attn_split.shape\n    B2, G2, H2, split_k1, M2 = lse_split.shape\n\n    assert (\n        B == B1 == B2\n        and G == G1 == G2\n        and H == H1 == H2\n        and M == M1 == M2\n        and Kq == Kq1\n    ), f\"Incompatible shapes: {attn_out.shape=}, {attn_split.shape=}, {lse_split.shape=}\"\n    assert (\n        split_k == split_k1\n    ), f\"Incompatible shapes: {attn_split.shape=}, {lse_split.shape=}\"\n    if lse_out is not None:\n        B3, G3, H3, M3 = lse_out.shape\n        assert (\n            B == B3 and G == G3 and H == H3 and M == M3\n        ), f\"Incompatible shapes: {attn_out.shape=}, {lse_out.shape=}\"\n\n    num_warps = 4 if B * G * H < 32 or torch.version.hip else 2\n    splitK_pow2 = triton.next_power_of_2(split_k)\n    head_dim = attn_out.shape[-1]\n    grid = (M, B * G * H, 1)\n    _splitK_reduce[grid](\n        attn_split,\n        lse_split,\n        attn_out,\n        lse_out,\n        split_k=split_k,\n        splitK_pow2=splitK_pow2,\n        **_strides(attn_split, \"osk_z\", \"osk_g\", \"osk_h\", \"osk_s\", \"osk_m\", \"osk_k\"),\n        **_strides(lse_split, \"lsek_z\", \"lsek_g\", \"lsek_h\", \"lsek_s\", \"lsek_m\"),\n        **_strides(attn_out, \"oz\", \"om\", \"og\", \"oh\", \"ok\"),\n        **_strides(lse_out, \"lse_z\", \"lse_g\", \"lse_h\", \"lse_m\"),\n        head_dim=head_dim,\n        head_dim_pow_2=triton.next_power_of_2(head_dim),\n        G=G,\n        H=H,\n        WRITE_LSE=lse_out is not None,\n        num_warps=num_warps,\n    )\n\n\n@torch.library.custom_op(\n    \"xformers::fmha_merge_attentions_varargs\",\n    mutates_args=(),\n    device_types=[\"cuda\"],\n)\ndef merge_attentions_varargs(\n    attn_split: Sequence[torch.Tensor],\n    lse_split: Sequence[torch.Tensor],\n    write_lse: bool,\n    output_dtype: Optional[torch.dtype],\n    B: int,\n    M: int,\n    G: int,\n    H: int,\n    Kq: int,\n) -> List[torch.Tensor]:\n    import triton\n\n    from xformers.triton.vararg_kernel import unroll_varargs\n\n    from ._triton.splitk_kernels import _splitK_reduce_varargs\n\n    attn_out = torch.empty(\n        (B, M, G, H, Kq),\n        device=attn_split[0].device,\n        dtype=output_dtype or attn_split[0].dtype,\n    )\n    if write_lse:\n        lse_out = torch.empty(\n            (B, G, H, M),\n            device=attn_split[0].device,\n            dtype=lse_split[0].dtype,\n        )\n    else:\n        lse_out = None\n    kernel_args, grid = _prepare_reduce_kernel_params(\n        attn_out, lse_out, attn_split, lse_split\n    )\n    reduce_kernel = unroll_varargs(_splitK_reduce_varargs, N=len(attn_split))\n    head_dim = attn_out.shape[-1]\n    reduce_kernel[grid](\n        *attn_split,\n        *lse_split,\n        Out=attn_out,\n        LSE=lse_out,\n        **kernel_args,\n        head_dim=head_dim,\n        head_dim_pow_2=triton.next_power_of_2(head_dim),\n        WRITE_LSE=lse_out is not None,\n    )\n    if write_lse:\n        assert lse_out is not None\n        return [attn_out, lse_out]\n    return [attn_out]\n\n\n@torch.library.register_fake(\"xformers::fmha_merge_attentions_varargs\")\ndef merge_attentions_varargs_fake(\n    attn_split: Sequence[torch.Tensor],\n    lse_split: Sequence[torch.Tensor],\n    write_lse: bool,\n    output_dtype: Optional[torch.dtype],\n    B: int,\n    M: int,\n    G: int,\n    H: int,\n    Kq: int,\n) -> List[torch.Tensor]:\n    attn_out = torch.empty(\n        (B, M, G, H, Kq),\n        device=attn_split[0].device,\n        dtype=output_dtype or attn_split[0].dtype,\n    )\n    if write_lse:\n        lse_out = torch.empty(\n            (B, G, H, M),\n            device=attn_split[0].device,\n            dtype=lse_split[0].dtype,\n        )\n        return [attn_out, lse_out]\n    return [attn_out]\n\n\ndef _merge_attentions_backward(\n    ctx: torch.autograd.function.FunctionCtx,\n    grad: List[torch.Tensor],\n) -> Tuple[None, ...]:\n    raise NotImplementedError(\n        \"Backward pass is not implemented for merge_attentions. \"\n        \"If it was, it would be easy to get wrong attention gradients, \"\n        \"because the gradients of the LSEs \"\n        \"don't get propagated by attention backward.\"\n    )\n\n\nmerge_attentions_varargs.register_autograd(_merge_attentions_backward)\n\n\n@torch.library.custom_op(\n    \"xformers::merge_attentions_varargs_backward\",\n    mutates_args=(),\n    device_types=[\"cuda\"],\n)\ndef merge_attentions_varargs_backward(\n    attn_split: List[torch.Tensor],\n    lse_split: List[torch.Tensor],\n    attn_out: torch.Tensor,\n    lse_out: torch.Tensor,\n    grad_attn: torch.Tensor,\n    grad_lse: torch.Tensor,\n) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:\n    from xformers.triton.vararg_kernel import unroll_varargs\n\n    from ._triton.splitk_kernels import _splitK_reduce_varargs_backward\n\n    dattn_splitk = [torch.empty_like(x) for x in attn_split]\n    dlse_splitk = [torch.empty_like(x) for x in lse_split]\n\n    kernel_args, grid = _prepare_reduce_kernel_params(\n        attn_out, lse_out, attn_split, lse_split, grad_attn, grad_lse\n    )\n\n    reduce_kernel_backward = unroll_varargs(\n        _splitK_reduce_varargs_backward, N=len(attn_split)\n    )\n    reduce_kernel_backward[grid](\n        *attn_split,\n        *lse_split,\n        *dattn_splitk,\n        *dlse_splitk,\n        Out=attn_out,\n        LSE=lse_out,\n        DOut=grad_attn,\n        DLSE=grad_lse,\n        **kernel_args,\n        BLOCK_SIZE=attn_out.shape[-1],\n    )\n\n    return dattn_splitk, dlse_splitk\n\n\n@torch.library.register_fake(\"xformers::merge_attentions_varargs_backward\")\ndef merge_attentions_varargs_backward_fake(\n    attn_split: List[torch.Tensor],\n    lse_split: List[torch.Tensor],\n    attn_out: torch.Tensor,\n    lse_out: torch.Tensor,\n    grad_attn: torch.Tensor,\n    grad_lse: torch.Tensor,\n) -> Tuple[List[torch.Tensor], List[torch.Tensor]]:\n    dattn_splitk = [torch.empty_like(x) for x in attn_split]\n    dlse_splitk = [torch.empty_like(x) for x in lse_split]\n    return dattn_splitk, dlse_splitk\n\n\ndef _prepare_reduce_kernel_params(\n    attn_out: torch.Tensor,\n    lse_out: Optional[torch.Tensor],\n    attn_split: Sequence[torch.Tensor],\n    lse_split: Sequence[torch.Tensor],\n    grad_attn: Optional[torch.Tensor] = None,\n    grad_lse: Optional[torch.Tensor] = None,\n) -> Tuple[Dict[str, int], Tuple[int, int, int]]:\n    B, M, G, H, Kq = attn_out.shape\n    B1, G1, H1, M1, Kq1 = attn_split[0].shape\n    B2, G2, H2, M2 = lse_split[0].shape\n\n    assert (\n        B == B1 == B2\n        and G == G1 == G2\n        and H == H1 == H2\n        and M == M1 == M2\n        and Kq == Kq1\n    ), f\"Incompatible shapes: {attn_out.shape=}, {attn_split[0].shape=}, {lse_split[0].shape=}\"\n    if lse_out is not None:\n        B3, G3, H3, M3 = lse_out.shape\n        assert (\n            B == B3 and G == G3 and H == H3 and M == M3\n        ), f\"Incompatible shapes: {attn_out.shape=}, {lse_out.shape=}\"\n\n    attn_split_strides = {}\n    lse_split_strides = {}\n    for i in range(len(attn_split)):\n        attn_split_strides.update(\n            _strides(\n                attn_split[i],\n                \"osk_z\" + str(i),\n                \"osk_g\" + str(i),\n                \"osk_h\" + str(i),\n                \"osk_m\" + str(i),\n                \"osk_k\" + str(i),\n            )\n        )\n        lse_split_strides.update(\n            _strides(\n                lse_split[i],\n                \"lsek_z\" + str(i),\n                \"lsek_g\" + str(i),\n                \"lsek_h\" + str(i),\n                \"lsek_m\" + str(i),\n            )\n        )\n\n    num_warps = 4 if B * G * H < 32 or torch.version.hip else 2\n    grid = (M, B * G * H, 1)\n\n    kernel_args = {\n        \"G\": G,\n        \"H\": H,\n        \"num_warps\": num_warps,\n        **attn_split_strides,\n        **lse_split_strides,\n    }\n    kernel_args.update(_strides(attn_out, \"oz\", \"om\", \"og\", \"oh\", \"ok\"))\n    kernel_args.update(_strides(lse_out, \"lse_z\", \"lse_g\", \"lse_h\", \"lse_m\"))\n    if grad_attn is not None:\n        kernel_args.update(_strides(grad_attn, \"doz\", \"dom\", \"dog\", \"doh\", \"dok\"))\n        kernel_args.update(_strides(grad_lse, \"dlse_z\", \"dlse_g\", \"dlse_h\", \"dlse_m\"))\n    return kernel_args, grid\n\n\nFwOp_Map = {\n    k: FwOp.get_operator(k) for k in [1, 2, 4, 8, 16, 32, 48, 64, 72, 80, 96, 112, 128]\n}\nFwOp_S1 = FwOp_Map[1]\nFwOp_S2 = FwOp_Map[2]\nFwOp_S4 = FwOp_Map[4]\nFwOp_S8 = FwOp_Map[8]\nFwOp_S16 = FwOp_Map[16]\nFwOp_S32 = FwOp_Map[32]\nFwOp_S64 = FwOp_Map[64]\nFwOp_S128 = FwOp_Map[128]\n"
  },
  {
    "path": "xformers/ops/indexing.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Optional, Sequence\n\nimport torch\n\nfrom xformers.ops._triton import (\n    index_select_cat_bwd,\n    index_select_cat_fwd,\n    scaled_index_add_bwd,\n    scaled_index_add_fwd,\n)\n\nfrom .common import BaseOperator, register_operator\n\n\n# Keeping these operator registry here so that\n# it's easy to check if they are available\n@register_operator\nclass ScaledIndexAddFw(BaseOperator):\n    OPERATOR = scaled_index_add_fwd\n    OPERATOR_CATEGORY = \"indexing\"\n    NAME = \"scaled_index_addF\"\n\n\n@register_operator\nclass ScaledIndexAddBw(BaseOperator):\n    OPERATOR = scaled_index_add_bwd\n    OPERATOR_CATEGORY = \"indexing\"\n    NAME = \"scaled_index_addB\"\n\n\n@register_operator\nclass IndexSelect(BaseOperator):\n    OPERATOR = index_select_cat_fwd\n    OPERATOR_CATEGORY = \"indexing\"\n    NAME = \"index_select\"\n\n\nclass _ScaledIndexAdd(torch.autograd.Function):\n    @staticmethod\n    # type: ignore\n    def forward(\n        ctx,\n        x: torch.Tensor,\n        index: torch.Tensor,\n        source: torch.Tensor,\n        scaling: Optional[torch.Tensor],\n        alpha: float,\n    ) -> torch.Tensor:\n        if scaled_index_add_fwd is not None:\n            scaled_index_add_fwd(x, index, source, scaling, alpha)\n        else:\n            raise RuntimeError(\n                \"Triton is needed for forward pass but it is not available!\"\n            )\n\n        ctx.mark_dirty(x)\n        ctx.save_for_backward(index, scaling, source)\n        ctx.source_shape = source.shape\n        ctx.alpha = alpha\n        return x\n\n    @staticmethod\n    @torch.autograd.function.once_differentiable\n    def backward(ctx, grad_output):\n        index, scaling, source = ctx.saved_tensors\n        grad_source = torch.empty_like(source)\n        grad_scaling = (\n            None\n            if scaling is None\n            else torch.empty(\n                ctx.source_shape, dtype=scaling.dtype, device=scaling.device\n            )\n        )\n\n        if scaled_index_add_bwd is not None:\n            scaled_index_add_bwd(\n                grad_output,\n                grad_source,\n                grad_scaling,\n                source,\n                scaling,\n                index,\n                ctx.alpha,\n            )\n        else:\n            raise RuntimeError(\n                \"Triton is needed for backward pass but it is not available!\"\n            )\n\n        return (\n            grad_output,  # gradient of input\n            None,  # gradient of index\n            grad_source,  # gradient of source\n            grad_scaling,  # gradient of scaling\n            None,  # gradient of alpha\n        )\n\n\ndef scaled_index_add(\n    input: torch.Tensor,  # [B, M, D]\n    index: torch.Tensor,  # [Bi] - int64\n    source: torch.Tensor,  # [Bi, M, D]\n    scaling: Optional[torch.Tensor] = None,  # [D]\n    alpha: float = 1.0,\n) -> torch.Tensor:\n    \"\"\"\n    In-place scaling+index_add\n\n    Indices in ``index`` are assumed to be unique\n\n    The max index in ``index`` is assumed to be less than the size of dim0 of ``input``.\n\n    :Note:\n\n        The FW pass is done in-place (``input`` is modified)\n\n    :Equivalent pytorch code:\n\n    .. code-block:: python\n\n        return torch.index_add(input, dim=0, source=scaling * src, index=indices, alpha=alpha)\n    \"\"\"\n\n    return _ScaledIndexAdd.apply(input, index, source, scaling, alpha)\n\n\nclass _IndexSelectCat(torch.autograd.Function):\n    @staticmethod\n    # type: ignore\n    def forward(\n        ctx,\n        *args: torch.Tensor,\n    ) -> torch.Tensor:\n        assert len(args) % 2 == 0\n        sources = args[: len(args) // 2]\n        indices = args[len(args) // 2 :]\n\n        output_numel = 0\n        for source, index in zip(sources, indices):\n            num_rows, num_cols = source.shape\n            num_indices = index.shape[0]\n            output_numel += num_indices * num_cols\n\n        output = torch.empty(\n            [output_numel], dtype=sources[0].dtype, device=sources[0].device\n        )\n\n        processed_numel = 0\n        for source, index in zip(sources, indices):\n            num_indices = index.shape[0]\n            num_cols = source.shape[1]\n\n            if index_select_cat_fwd is not None:\n                index_select_cat_fwd(\n                    output[\n                        processed_numel : processed_numel + num_indices * num_cols\n                    ].view([num_indices, num_cols]),\n                    source,\n                    index,\n                )\n            else:\n                raise RuntimeError(\n                    \"Triton is needed for forward pass but it is not available!\"\n                )\n\n            processed_numel += num_indices * num_cols\n\n        ctx.save_for_backward(*indices)\n        ctx.source_shapes = [source.shape for source in sources]\n\n        return output\n\n    @staticmethod\n    @torch.autograd.function.once_differentiable\n    def backward(ctx, grad_output):\n        indices = ctx.saved_tensors\n\n        gradients = []\n        processed_numel = 0\n        for source_shape, index in zip(ctx.source_shapes, indices):\n            num_rows, num_cols = source_shape\n            num_indices = index.shape[0]\n\n            grad_output_slice = grad_output[\n                processed_numel : processed_numel + num_indices * num_cols\n            ].reshape([num_indices, num_cols])\n            processed_numel += num_indices * num_cols\n\n            grad_source_slice = torch.zeros(\n                [num_rows, num_cols],\n                dtype=grad_output.dtype,\n                device=grad_output.device,\n            )\n\n            if index_select_cat_bwd is not None:\n                index_select_cat_bwd(\n                    grad_source_slice,\n                    index,\n                    grad_output_slice,\n                )\n            else:\n                raise RuntimeError(\n                    \"Triton is needed for backward pass but it is not available!\"\n                )\n            gradients.append(grad_source_slice)\n\n        return (*gradients, *([None] * len(gradients)))\n\n\ndef index_select_cat(\n    sources: Sequence[torch.Tensor], indices: Sequence[torch.Tensor]\n) -> torch.Tensor:\n    \"\"\"\n    Indices in ``index`` are assumed to be unique\n    In each (index, source) pair, the max index in ``index`` is assumed to be less than the size of dim0 of ``source``\n\n    :Example:\n\n    Given:\n    - ``sources[0]`` of shape ``[S0, D0]``\n    - ``indices[0]`` of shape ``[I0]``\n    - ``sources[1]`` of shape ``[S1, D1]``\n    - ``indices[1]`` of shape ``[I1]``\n    returns a ``torch.Tensor`` of shape ``[I0 * D0 + I1 * D1]``\n\n    :Equivalent pytorch code:\n\n    .. code-block:: python\n\n        return torch.cat([s[i.long()].flatten() for s, i in zip(sources, indices)], dim=0)\n    \"\"\"\n    return _IndexSelectCat.apply(*sources, *indices)\n"
  },
  {
    "path": "xformers/ops/modpar_layers.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Callable, List, Optional\n\nimport torch\nimport torch.distributed\n\nfrom .differentiable_collectives import (\n    copy_to_model_parallel_region,\n    reduce_from_model_parallel_region,\n)\nfrom .seqpar import sequence_parallel_leading_matmul, sequence_parallel_trailing_matmul\n\n\ndef _init_2d_weight(\n    weight: torch.Tensor,\n    init_method: Callable[[torch.Tensor], torch.Tensor],\n    process_group: Optional[torch.distributed.ProcessGroup],\n    partition_dim: int,\n) -> None:\n    # Mimick FairScale's _initialize_affine_weight, for backwards compatibility.\n    # The reason we initialize the full unpartitioned/gathered weight is so that\n    # different ranks get different initial values and thus \"break the symmetry\"\n    # and in order to achieve the same init for any value of model parallelism.\n    rank = process_group.rank() if process_group is not None else 0\n    world_size = process_group.size() if process_group is not None else 1\n\n    nrows, ncols = weight.shape\n    if partition_dim == 0:\n        full_weight = weight.new_empty(nrows * world_size, ncols)\n        my_weight_slice = full_weight[rank::world_size, :]\n    else:\n        full_weight = weight.new_empty(nrows, ncols * world_size)\n        my_weight_slice = full_weight[:, rank::world_size]\n\n    init_method(full_weight)\n\n    with torch.no_grad():\n        weight.copy_(my_weight_slice)\n\n\nclass ColumnParallelLinear(torch.nn.Module):\n    def __init__(\n        self,\n        in_features: int,\n        out_features: List[int],\n        *,\n        process_group: torch.distributed.ProcessGroup,\n        bias: bool = True,\n        gather_output: bool = True,\n        init_method: Callable[\n            [torch.Tensor], torch.Tensor\n        ] = torch.nn.init.xavier_normal_,\n        sequence_parallel: bool = False,\n        fuse_sequence_parallel: bool = True,\n    ) -> None:\n        super(ColumnParallelLinear, self).__init__()\n\n        if not isinstance(out_features, list):\n            raise TypeError(\n                \"xFormers's implementation of ColumnParallelLinear requires out_features to be a list\"\n            )\n        if bias:\n            raise ValueError(\n                \"xFormers's implementation of ColumnParallelLinear requires bias=False\"\n            )\n        if gather_output:\n            raise ValueError(\n                \"xFormers's implementation of ColumnParallelLinear requires gather_output=False\"\n            )\n\n        self.in_features = in_features\n        self.global_out_features = out_features\n        self.sequence_parallel = sequence_parallel\n        self.fuse_sequence_parallel = fuse_sequence_parallel\n        self.process_group = process_group\n        mp_size = process_group.size()\n        assert all(dim % mp_size == 0 for dim in out_features)\n        self.my_out_features = [dim // mp_size for dim in out_features]\n\n        self.weights = torch.nn.ParameterList(\n            [\n                torch.nn.Parameter(torch.empty((dim, in_features)))\n                for dim in self.my_out_features\n            ]\n        )\n\n        for w in self.weights:\n            _init_2d_weight(w, init_method, process_group, partition_dim=0)\n\n    def forward(self, input_: torch.Tensor) -> List[torch.Tensor]:\n        if self.sequence_parallel:\n            outputs = sequence_parallel_leading_matmul(\n                input_,\n                [w.t() for w in self.weights],\n                fuse=self.fuse_sequence_parallel,\n                process_group=self.process_group,\n            )\n        else:\n            input_ = copy_to_model_parallel_region(input_, self.process_group)\n            outputs = [torch.matmul(input_, w.t()) for w in self.weights]\n        return outputs\n\n\nclass RowParallelLinear(torch.nn.Module):\n    def __init__(\n        self,\n        in_features: int,\n        out_features: int,\n        *,\n        process_group: torch.distributed.ProcessGroup,\n        bias: bool = True,\n        input_is_parallel: bool = False,\n        init_method: Callable[\n            [torch.Tensor], torch.Tensor\n        ] = torch.nn.init.xavier_normal_,\n        sequence_parallel: bool = False,\n        fuse_sequence_parallel: bool = True,\n    ):\n        super(RowParallelLinear, self).__init__()\n\n        if bias:\n            raise ValueError(\n                \"xFormers's implementation of RowParallelLinear requires bias=False\"\n            )\n        if not input_is_parallel:\n            raise ValueError(\n                \"xFormers's implementation of RowParallelLinear requires input_is_parallel=True\"\n            )\n\n        self.global_in_features = in_features\n        self.out_features = out_features\n        self.sequence_parallel = sequence_parallel\n        self.fuse_sequence_parallel = fuse_sequence_parallel\n        self.process_group = process_group\n        mp_size = process_group.size()\n        assert in_features % mp_size == 0\n        self.my_in_features = in_features // mp_size\n\n        self.weight = torch.nn.Parameter(\n            torch.empty((out_features, self.my_in_features))\n        )\n\n        _init_2d_weight(self.weight, init_method, process_group, partition_dim=1)\n\n    def forward(self, input_: torch.Tensor) -> torch.Tensor:\n        if self.sequence_parallel:\n            output = sequence_parallel_trailing_matmul(\n                input_,\n                self.weight.t(),\n                fuse=self.fuse_sequence_parallel,\n                process_group=self.process_group,\n            )\n        else:\n            output = torch.matmul(input_, self.weight.t())\n            output = reduce_from_model_parallel_region(output, self.process_group)\n        return output\n"
  },
  {
    "path": "xformers/ops/rmsnorm.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nfrom typing import Optional\n\nimport torch\nfrom torch import nn\n\nfrom .. import _is_triton_available\n\n\ndef rms_norm(x, weight: Optional[torch.Tensor], eps: float = 1e-6):\n    \"\"\"\n    RMS Normalization along the last dimension.\n\n    This is similar to torch.nn.functional.normalize but with eps being added\n    instead of max.\n\n    Expects x contiguous of shape (..., dim), and returns normalized data\n    of the same shape. For each dim-length vector x, the result has\n\n        x / sqrt( x*x.sum() + eps)\n\n    If weights are included, they are a contiguous parameter of length dim\n    which multiplies the result.\n\n    This functionality is experimental. Its API might be changed without warnings.\n    Use it at your own risk.\n    \"\"\"\n    assert _is_triton_available()\n    from ._triton.rmsnorm_kernels import _rms_norm_forward\n\n    if torch.is_grad_enabled() and (\n        x.requires_grad or (weight is not None and weight.requires_grad)\n    ):\n        raise ValueError(\"Gradients not supported.\")\n\n    return _rms_norm_forward(x, weight, eps)\n\n\ndef rms_norm_add(\n    x: torch.Tensor, y: torch.Tensor, weight: Optional[torch.Tensor], eps: float = 1e-6\n):\n    \"\"\"\n    An addition fused with rms_norm.\n\n        z = rms_norm_add(x, y, weight, eps)\n\n    is equivalent to\n\n        x += y\n        z = rms_norm(x, weight, eps)\n\n    where x, y and z are all contiguous.\n\n    This functionality is experimental. Its API might be changed without warnings.\n    Use it at your own risk.\n    \"\"\"\n    if torch.is_grad_enabled() and (\n        x.requires_grad\n        or y.requires_grad\n        or (weight is not None and weight.requires_grad)\n    ):\n        raise ValueError(\"Gradients not supported.\")\n    assert _is_triton_available()\n    from ._triton.rmsnorm_kernels import _rms_norm_add_forward\n\n    return _rms_norm_add_forward(x, y, weight, eps)\n\n\nclass RMSNorm(torch.nn.Module):\n    \"\"\"\n    RMS Normalization layer along the last dimension.\n\n    This is similar to torch.nn.functional.normalize but with eps being added\n    instead of max.\n\n    Expects contiguous input of shape (..., dim), and returns normalized data\n    of the same shape. For each dim-length vector x, the result has\n\n        x / sqrt( x*x.sum() + eps)\n\n    If weights are included, they are a parameter of length dim which multiplies\n    the result.\n\n    This functionality is experimental. Its API might be changed without warnings.\n    Use it at your own risk.\n    \"\"\"\n\n    def __init__(self, dim: int, include_weight: bool = True, eps: float = 1e-6):\n        super().__init__()\n        self.eps = eps\n        if include_weight:\n            self.weight: Optional[nn.Parameter] = nn.Parameter(torch.ones(dim))\n        else:\n            self.weight = None\n\n    def forward(self, x: torch.Tensor):\n        return rms_norm(x, self.weight, self.eps)  # type: ignore\n\n    def increment_and_forward_(self, x: torch.Tensor, y: torch.Tensor):\n        \"\"\"\n        An addition fused with forward.\n\n            z = layer.increment_and_forward_(x, y)\n\n        is equivalent to\n\n            x += y\n            z = layer(x)\n        \"\"\"\n        return rms_norm_add(x, y, self.weight, self.eps)  # type: ignore\n"
  },
  {
    "path": "xformers/ops/rope_padded.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\nfrom typing import Optional, Tuple\n\nimport torch\n\nfrom xformers.ops.fmha.attn_bias import (  # type: ignore\n    BlockDiagonalCausalWithOffsetPaddedKeysMask,\n)\n\nfrom .. import _is_triton_available\n\n\ndef rope_padded(\n    xq: torch.Tensor,\n    xk: torch.Tensor,\n    xv: torch.Tensor,\n    cache_k: torch.Tensor,\n    cache_v: torch.Tensor,\n    attn_bias: BlockDiagonalCausalWithOffsetPaddedKeysMask,\n    *,\n    theta: float = 10000.0,\n    linear_scale: float = 1.0,\n    use_dynamic_scaling: bool = False,\n    dynamic_old_context_len: float = 8192.0,\n    dynamic_scale_factor: float = 16.0,\n    dynamic_low_freq_factor: float = 1.0,\n    dynamic_high_freq_factor: float = 32.0,\n    out_q: Optional[torch.Tensor] = None,\n    first_seqpos: Optional[torch.Tensor] = None,\n    seqpos: Optional[torch.Tensor] = None,\n    adjacents: bool = True,\n    internal_dtype: str = \"\",\n):\n    \"\"\"\n    Performs RoPE (rotary embeddings) and kv-cache emplacement for a heterogeneous\n    batch for inference in the style given by\n    BlockDiagonalCausalWithOffsetPaddedKeysMask.\n    The batch is concatenated along the sequence dimension, so the\n    actual dim-0 length of all tensors is 1.\n\n    xq, xk and xv should be (1, slen, n_heads, dim), where\n    xq's n_heads can differ from xk and xv.\n\n    This function places the roped xk in the right place in cache_k, and\n    xv (unmodified) in the right place in cache_v, and returns out_q\n    (the roped xq) such that things are ready to call\n\n    xformers.ops.memory_efficient_attention(\n        out_q, cache_k, cache_v, attn_bias=attn_bias\n    )\n\n    This functionality is experimental. Its API might be changed without warnings.\n    Use it at your own risk.\n\n    Arguments:\n        xq: tensor of queries to apply rope to\n        xk: tensor of keys to apply rope to\n        xv: tensor of values to copy into cache_v\n        cache_k: cache of keys, MODIFIED IN PLACE\n        cache_v: cache of values, MODIFIED IN PLACE\n        attn_bias: details the layout of caches.\n                Used to determine frequencies for the\n                RoPE calculation as well as the locations in cache_k and cache_v\n                to write to. Must be on the device.\n        first_seqpos: Optionally a tensor containing the sequence position of the\n                    beginning of the cache for each batch element.\n                    Providing a tensor of zeros is the same as providing None.\n                    This affects the numerical calculation but not which memory\n                    locations are read or written.\n        seqpos: Optionally a 1D tensor containing the sequence position of each\n                    query. This should have length equal to xq.shape[1] .\n                    This affects the numerical calculation but not which memory\n                    locations are read or written.\n        adjacents: If True, the inputs are in adjacent pairs along the final dim axis.\n                  This is like the released LLaMA model.\n                  If False, the dim axis is split in two equal pieces.\n                   I.e. the features are ordered with all the real parts before all\n                   the imaginary parts. This matches HuggingFace, e.g.\n                   https://github.com/huggingface/transformers/blob/\n                   f143037789288ba532dada934a118e648e715738/\n                   src/transformers/models/llama/modeling_llama.py#L126-L130\n        linear_scale: A scaling factor to apply to the sequence ids when computing\n                      the RoPE frequencies.  When set to K, all sequence indices\n                      are divided by K.\n        use_dynamic_scaling: If true, dynamic scaling in use, using a scaling like\n            “YaRN: Efficient Context Window Extension of Large Language Models”\n        dynamic_old_context_len: used with use_dynamic_scaling\n        dynamic_scale_factor: used with use_dynamic_scaling\n        dynamic_low_freq_factor: used with use_dynamic_scaling\n        dynamic_high_freq_factor: used with use_dynamic_scaling\n        internal_dtype: set to \"f32\" or \"f64\" to enforce dtype in the calculation\n    \"\"\"\n    if torch.is_grad_enabled() and (\n        xq.requires_grad\n        or xk.requires_grad\n        or xv.requires_grad\n        or cache_k.requires_grad\n        or cache_v.requires_grad\n        or out_q is not None\n    ):\n        raise ValueError(\"Gradients not supported.\")\n    assert _is_triton_available()\n    import triton\n\n    from ._triton.rope_padded_kernels import _rope_padded_kernel\n\n    n_total_queries = attn_bias.q_seqinfo.seqstart_py[-1]\n    cache_length = attn_bias.k_seqinfo.seqstart_py[-1]\n    ndim = xq.ndim\n    if ndim not in [4, 5]:\n        raise ValueError(\"Unexpected xq dimension\")\n    xq_stride = xq.stride()\n    xk_stride = xk.stride()\n    xv_stride = xv.stride()\n    cache_k_stride = cache_k.stride()\n    cache_v_stride = cache_v.stride()\n    cache_k_shape = cache_k.shape\n    xk_shape = xk.shape\n    n_kv_heads = xk_shape[-2]\n    expected_kv_heads = n_kv_heads\n    if xk_stride[-2] == 0:\n        n_kv_heads = 1\n    expected_cache_heads = n_kv_heads\n    if n_kv_heads == 1 and cache_k_stride[-2] == 0:\n        # If there's 1 kv head, don't care how expanded\n        # cache_k is. User might expand before or after rope.\n        expected_cache_heads = cache_k_shape[-2]\n\n    if ndim == 4:\n        bsz, q_len, n_q_heads, dim = xq.shape\n        assert q_len == n_total_queries\n        if xk_shape != (1, n_total_queries, expected_kv_heads, dim):\n            raise ValueError(\n                f\"unexpected k shape {xk_shape}: expected {(1, n_total_queries, expected_kv_heads, dim)}\"\n            )\n        if xv.shape != (1, n_total_queries, expected_kv_heads, dim):\n            raise ValueError(\n                f\"unexpected v shape {xv.shape}: expected {(1, n_total_queries, expected_kv_heads, dim)}\"\n            )\n        if cache_k_shape != (1, cache_length, expected_cache_heads, dim):\n            raise ValueError(\"unexpected cache_k shape\")\n        if cache_v.shape != (1, cache_length, expected_cache_heads, dim):\n            raise ValueError(\"unexpected cache_v shape\")\n        n_groups = 1\n        out_q_stride: Tuple[int, ...] = (0, n_q_heads * dim, dim, 1)\n\n    else:\n        bsz, q_len, n_groups, n_q_heads, dim = xq.shape\n        assert q_len == n_total_queries\n        if xk_shape != (1, n_total_queries, n_groups, expected_kv_heads, dim):\n            raise ValueError(\n                f\"unexpected k shape {xk_shape}: expected {(1, n_total_queries, n_groups, expected_kv_heads, dim)}\"\n            )\n        if xv.shape != (1, n_total_queries, n_groups, expected_kv_heads, dim):\n            raise ValueError(\n                f\"unexpected v shape {xv.shape}: expected {(1, n_total_queries, n_groups, expected_kv_heads, dim)}\"\n            )\n        if cache_k_shape != (1, cache_length, n_groups, expected_cache_heads, dim):\n            raise ValueError(\n                f\"unexpected cache_k shape {cache_k_shape}: \"\n                f\"expected {(1, cache_length, n_groups, expected_cache_heads, dim)}\"\n            )\n        if cache_v.shape != (1, cache_length, n_groups, expected_cache_heads, dim):\n            raise ValueError(\n                f\"unexpected cache_v shape {cache_v.shape}: \"\n                f\"expected {(1, cache_length, n_groups, expected_cache_heads, dim)}\"\n            )\n        out_q_stride = (\n            0,\n            n_q_heads * dim * n_groups,\n            n_q_heads * dim,\n            dim,\n            1,\n        )\n\n    if bsz != 1:\n        raise ValueError(\n            \"Expected batch size dimension to be 1 as batches should be concatenated.\"\n        )\n    if xq_stride[-1] != 1:\n        raise ValueError(\"Each q head must be contiguous\")\n    if xk_stride[-1] != 1:\n        raise ValueError(\"Each k head must be contiguous\")\n    if xv_stride[-1] != 1:\n        raise ValueError(\"Each v head must be contiguous\")\n    if cache_k_stride[-1] != 1:\n        raise ValueError(\"Each cache_k head must be contiguous\")\n    if cache_v_stride[-1] != 1:\n        raise ValueError(\"Each cache_v head must be contiguous\")\n    n_total_heads = n_q_heads + 2 * n_kv_heads\n    v_start = n_total_heads - n_kv_heads\n    k_start = n_q_heads\n    if out_q is None:\n        out_q = xq.new_empty(xq.shape)\n    else:\n        if out_q.shape != xq.shape:\n            raise ValueError(\"Unexpected shape of out_q\")\n        out_q_stride = out_q.stride()\n        if out_q_stride[-1] != 1:\n            raise ValueError(\"Each out_q head must be contiguous\")\n\n    assert out_q is not None\n\n    logical_bsz = len(attn_bias.q_seqinfo.seqstart_py) - 1\n\n    if first_seqpos is not None and seqpos is not None:\n        raise ValueError(\"seqpos and first_seqpos may not both be provided\")\n    stride_seqpos = 0\n    if first_seqpos is not None:\n        if first_seqpos.shape != (logical_bsz,):\n            shape = tuple(first_seqpos.shape)\n            raise ValueError(\n                f\"first_seqpos.shape {shape} but ({logical_bsz},) expected.\"\n            )\n        stride_seqpos = first_seqpos.stride(0)\n    elif seqpos is not None:\n        if seqpos.shape != (n_total_queries,):\n            shape = tuple(seqpos.shape)\n            raise ValueError(f\"seqpos.shape {shape} but ({n_total_queries},) expected.\")\n        stride_seqpos = seqpos.stride(0)\n\n    # Less than 64KB per feature: enqueue fused kernel\n    MAX_FUSED_SIZE = 65536 // xq.element_size()\n    BLOCK_SIZE = min(MAX_FUSED_SIZE, triton.next_power_of_2(dim))\n    BLOCK_SIZE = max(BLOCK_SIZE, 128)\n    BLOCK_SIZE = min(BLOCK_SIZE, 4096)\n    # heuristics for number of warps\n    num_warps = min(max(BLOCK_SIZE // 256, 1), 8)\n    device = xq.device\n    seqstartq = attn_bias.q_seqinfo.seqstart\n    seqstartk = attn_bias.k_seqinfo.seqstart\n    seqlenk = attn_bias.k_seqinfo.seqlen\n    if (\n        seqstartq.device != device\n        or seqstartk.device != device\n        or seqlenk.device != device\n    ):\n        raise ValueError(\"`attn_bias` must be on the same device as the other inputs\")\n    assert internal_dtype in [\"\", \"f32\", \"f64\"]\n    # experiment with the order of dims here.\n    with torch.cuda.device(xq.device):\n        _rope_padded_kernel[\n            (attn_bias.q_seqinfo.max_seqlen, logical_bsz, n_total_heads * n_groups)\n        ](\n            xq,\n            xk,\n            xv,\n            out_q,\n            cache_k,\n            cache_v,\n            seqstartq,\n            seqstartk,\n            seqlenk,\n            theta,\n            linear_scale,\n            use_dynamic_scaling,\n            dynamic_old_context_len if use_dynamic_scaling else 0,\n            dynamic_scale_factor if use_dynamic_scaling else 0,\n            dynamic_low_freq_factor if use_dynamic_scaling else 0,\n            dynamic_high_freq_factor if use_dynamic_scaling else 0,\n            first_seqpos,\n            seqpos,\n            k_start,\n            v_start,\n            n_groups,\n            dim,\n            xq_stride[1],\n            xq_stride[2] if ndim == 5 else 0,\n            xq_stride[-2],\n            xk_stride[1],\n            xk_stride[2] if ndim == 5 else 0,\n            xk_stride[-2],\n            xv_stride[1],\n            xv_stride[2] if ndim == 5 else 0,\n            xv_stride[-2],\n            cache_k_stride[1],\n            cache_k_stride[2] if ndim == 5 else 0,\n            cache_k_stride[-2],\n            cache_v_stride[1],\n            cache_v_stride[2] if ndim == 5 else 0,\n            cache_v_stride[-2],\n            seqstartq.stride(0),\n            seqstartk.stride(0),\n            seqlenk.stride(0),\n            out_q_stride[1],\n            out_q_stride[2] if ndim == 5 else 0,\n            out_q_stride[-2],\n            stride_seqpos,\n            internal_dtype,\n            const_batch_strides=False,\n            cache_padding_length=0,\n            seqlenk_shift=0,\n            BLOCK_SIZE=BLOCK_SIZE,\n            adjacents=adjacents,\n            num_warps=num_warps,\n        )\n    return out_q\n"
  },
  {
    "path": "xformers/ops/seqpar.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import Callable, List, Tuple\n\nimport torch\nfrom torch.distributed.distributed_c10d import _resolve_process_group, GroupName\n\nfrom .differentiable_collectives import (\n    gather_along_first_dim,\n    gather_along_first_dim_async,\n    reduce_scatter_along_first_dim,\n    reduce_scatter_along_first_dim_async,\n)\nfrom .sequence_parallel_fused_ops import (\n    fused_allgather_and_anything,\n    fused_allgather_and_linear,\n    fused_anything_and_reducescatter,\n    fused_linear_and_reducescatter,\n)\nfrom .tiled_matmul import tiled_matmul, tiled_matmul_out\n\n\n@torch.library.custom_op(\n    \"xformers_python::sequence_parallel_leading_matmul_fwd\",\n    mutates_args=(),\n    device_types=\"cuda\",\n)\ndef sequence_parallel_leading_matmul_fwd(\n    scattered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    fuse: bool,\n    process_group_name: GroupName,\n) -> List[torch.Tensor]:\n    process_group = _resolve_process_group(process_group_name)\n\n    if fuse:\n        gathered_outputs = fused_allgather_and_linear(\n            scattered_input, [w.t() for w in weights], group=process_group\n        )\n    else:\n        gathered_input = gather_along_first_dim(\n            scattered_input, process_group=process_group\n        )\n        (gathered_outputs,) = tiled_matmul(\n            [[gathered_input]],\n            [[w for w in weights]],\n        )\n    return gathered_outputs\n\n\n@torch.library.register_fake(\"xformers_python::sequence_parallel_leading_matmul_fwd\")\ndef sequence_parallel_leading_matmul_fwd_fake(\n    scattered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    fuse: bool,\n    process_group_name: GroupName,\n) -> List[torch.Tensor]:\n    mp_size = _resolve_process_group(process_group_name).size()\n    return [\n        scattered_input.new_empty((scattered_input.shape[0] * mp_size, w.shape[1]))\n        for w in weights\n    ]\n\n\n@torch.library.custom_op(\n    \"xformers_python::sequence_parallel_leading_matmul_bwd\",\n    mutates_args=(),\n    device_types=\"cuda\",\n)\ndef sequence_parallel_leading_matmul_bwd(\n    scattered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    grad_gathered_outputs: List[torch.Tensor],\n    fuse: bool,\n    process_group_name: GroupName,\n) -> Tuple[torch.Tensor, List[torch.Tensor]]:\n    process_group = _resolve_process_group(process_group_name)\n    mp_size = process_group.size()\n\n    # torch.library.opcheck gives us gradients whose strides are zero.\n    # See https://github.com/pytorch/pytorch/issues/132857.\n    grad_gathered_outputs = [\n        grad_go.clone() if any(s == 0 for s in grad_go.stride()) else grad_go\n        for grad_go in grad_gathered_outputs\n    ]\n\n    if fuse:\n        grad_scattered_input = torch.empty_like(scattered_input)\n        grad_weights = [torch.zeros_like(w) for w in weights]\n\n        grad_gathered_outputss = [\n            grad_go.tensor_split(mp_size, dim=0) for grad_go in grad_gathered_outputs\n        ]\n\n        def my_si_matmul(\n            grad_gathered_inputs: List[torch.Tensor],\n            dst_rank: int,\n            stream_factory: Callable[[], torch.cuda.Stream],\n        ) -> None:\n            (grad_gi,) = grad_gathered_inputs\n            with torch.cuda.stream(stream_factory()):\n                tiled_matmul_out(\n                    [[grad_gos[dst_rank] for grad_gos in grad_gathered_outputss]],\n                    [[w.t()] for w in weights],\n                    out=[[grad_gi]],\n                )\n\n        fused_anything_and_reducescatter(\n            my_si_matmul,\n            [grad_scattered_input],\n            group=process_group,\n        )\n\n        # Each pair of shards of input and grad_output accumulates into the same\n        # grad_weight. Thus we need to make sure that the in-place addmms are\n        # sequenced correctly for each of the grad_weights.\n        events = [torch.cuda.Event() for _ in weights]\n\n        def my_w_matmul(\n            gathered_inputs_shard: List[torch.Tensor],\n            src_rank: int,\n            stream_factory: Callable[[], torch.cuda.Stream],\n        ) -> None:\n            (gi_shard,) = gathered_inputs_shard\n            for grad_gos, grad_w, event in zip(\n                grad_gathered_outputss, grad_weights, events\n            ):\n                with torch.cuda.stream(stream_factory()):\n                    event.wait()\n                    grad_w.t().addmm_(grad_gos[src_rank].t(), gi_shard)\n                    event.record()\n\n        fused_allgather_and_anything(\n            [scattered_input],\n            my_w_matmul,\n            group=process_group,\n        )\n    else:\n        gathered_input, handle = gather_along_first_dim_async(\n            scattered_input, process_group=process_group\n        )\n        ((grad_gathered_input,),) = tiled_matmul(\n            [[grad_go for grad_go in grad_gathered_outputs]],\n            [[w.t()] for w in weights],\n        )\n        if handle is not None:\n            handle.wait()\n\n        grad_scattered_input, handle = reduce_scatter_along_first_dim_async(\n            grad_gathered_input, process_group=process_group\n        )\n\n        grad_weights_tuples = tiled_matmul(\n            [[grad_go.t()] for grad_go in grad_gathered_outputs],\n            [[gathered_input]],\n        )\n        if handle is not None:\n            handle.wait()\n\n        grad_weights = [grad_w.t() for (grad_w,) in grad_weights_tuples]\n\n    return grad_scattered_input, grad_weights\n\n\n@torch.library.register_fake(\"xformers_python::sequence_parallel_leading_matmul_bwd\")\ndef sequence_parallel_leading_matmul_bwd_fake(\n    scattered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    grad_gathered_outputs: List[torch.Tensor],\n    fuse: bool,\n    process_group_name: GroupName,\n) -> Tuple[torch.Tensor, List[torch.Tensor]]:\n    return (torch.empty_like(scattered_input), [torch.empty_like(w) for w in weights])\n\n\ndef sequence_parallel_leading_matmul_setup_context(ctx, inputs, output):\n    scattered_input, weights, fuse, process_group_name = inputs\n    ctx.save_for_backward(scattered_input, *weights)\n    ctx.fuse = fuse\n    ctx.process_group_name = process_group_name\n\n\ndef sequence_parallel_leading_matmul_bwd_bridge(ctx, grad_gathered_outputs):\n    scattered_input, *weights = ctx.saved_tensors\n    (\n        grad_scattered_input,\n        grad_weights,\n    ) = sequence_parallel_leading_matmul_bwd(\n        scattered_input,\n        list(weights),\n        list(grad_gathered_outputs),\n        ctx.fuse,\n        ctx.process_group_name,\n    )\n    return grad_scattered_input, grad_weights, None, None\n\n\ntorch.library.register_autograd(\n    \"xformers_python::sequence_parallel_leading_matmul_fwd\",\n    sequence_parallel_leading_matmul_bwd_bridge,\n    setup_context=sequence_parallel_leading_matmul_setup_context,\n)\n\n\ndef sequence_parallel_leading_matmul(\n    x: torch.Tensor,\n    ws: List[torch.Tensor],\n    *,\n    fuse: bool,\n    process_group: torch.distributed.ProcessGroup,\n) -> List[torch.Tensor]:\n    os = sequence_parallel_leading_matmul_fwd(\n        x.flatten(0, -2), ws, fuse, process_group.group_name\n    )\n    return [o.view(-1, *x.shape[1:-1], w.shape[1]) for o, w in zip(os, ws)]\n\n\n@torch.library.custom_op(\n    \"xformers_python::sequence_parallel_trailing_matmul_fwd\",\n    mutates_args=(),\n    device_types=\"cuda\",\n)\ndef sequence_parallel_trailing_matmul_fwd(\n    gathered_input: torch.Tensor,\n    weight: torch.Tensor,\n    fuse: bool,\n    process_group_name: GroupName,\n) -> torch.Tensor:\n    process_group = _resolve_process_group(process_group_name)\n\n    if fuse:\n        scattered_output = fused_linear_and_reducescatter(\n            gathered_input, weight.t(), group=process_group\n        )\n    else:\n        gathered_output = torch.matmul(gathered_input, weight)\n        scattered_output = reduce_scatter_along_first_dim(\n            gathered_output, process_group=process_group\n        )\n    return scattered_output\n\n\n@torch.library.register_fake(\"xformers_python::sequence_parallel_trailing_matmul_fwd\")\ndef sequence_parallel_trailing_matmul_fwd_fake(\n    gathered_input: torch.Tensor,\n    weight: torch.Tensor,\n    fuse: bool,\n    process_group_name: GroupName,\n) -> torch.Tensor:\n    mp_size = _resolve_process_group(process_group_name).size()\n    return gathered_input.new_empty(\n        (gathered_input.shape[0] // mp_size, weight.shape[1])\n    )\n\n\n@torch.library.custom_op(\n    \"xformers_python::sequence_parallel_trailing_matmul_bwd\",\n    mutates_args=(),\n    device_types=\"cuda\",\n)\ndef sequence_parallel_trailing_matmul_bwd(\n    gathered_input: torch.Tensor,\n    weight: torch.Tensor,\n    grad_scattered_output: torch.Tensor,\n    fuse: bool,\n    process_group_name: GroupName,\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    process_group = _resolve_process_group(process_group_name)\n    mp_size = process_group.size()\n\n    # torch.library.opcheck gives us gradients whose strides are zero.\n    # See https://github.com/pytorch/pytorch/issues/132857.\n    if any(s == 0 for s in grad_scattered_output.stride()):\n        grad_scattered_output = grad_scattered_output.clone()\n\n    if fuse:\n        grad_gathered_input = torch.empty_like(gathered_input)\n        grad_weight = torch.zeros_like(weight)\n\n        gathered_inputs = gathered_input.tensor_split(mp_size, dim=0)\n        grad_gathered_inputs = grad_gathered_input.tensor_split(mp_size, dim=0)\n\n        def my_gi_and_w_matmul(\n            grad_gathered_outputs_shard: List[torch.Tensor],\n            src_rank: int,\n            stream_factory: Callable[[], torch.cuda.Stream],\n        ) -> None:\n            (grad_go_shard,) = grad_gathered_outputs_shard\n            with torch.cuda.stream(stream_factory()):\n                torch.matmul(\n                    grad_go_shard, weight.t(), out=grad_gathered_inputs[src_rank]\n                )\n            with torch.cuda.stream(stream_factory()):\n                grad_weight.t().addmm_(grad_go_shard.t(), gathered_inputs[src_rank])\n\n        fused_allgather_and_anything(\n            [grad_scattered_output],\n            my_gi_and_w_matmul,\n            group=process_group,\n        )\n    else:\n        grad_gathered_output = gather_along_first_dim(\n            grad_scattered_output, process_group=process_group\n        )\n        grad_gathered_input = torch.matmul(grad_gathered_output, weight.t())\n        grad_weight = torch.matmul(grad_gathered_output.t(), gathered_input).t()\n\n    return grad_gathered_input, grad_weight\n\n\n@torch.library.register_fake(\"xformers_python::sequence_parallel_trailing_matmul_bwd\")\ndef sequence_parallel_trailing_matmul_bwd_fake(\n    gathered_input: torch.Tensor,\n    weight: torch.Tensor,\n    grad_scattered_output: torch.Tensor,\n    fuse: bool,\n    process_group_name: GroupName,\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    return (torch.empty_like(gathered_input), torch.empty_like(weight))\n\n\ndef sequence_parallel_trailing_matmul_setup_context(ctx, inputs, output):\n    gathered_input, weight, fuse, process_group_name = inputs\n    ctx.save_for_backward(gathered_input, weight)\n    ctx.fuse = fuse\n    ctx.process_group_name = process_group_name\n\n\ndef sequence_parallel_trailing_matmul_bwd_bridge(ctx, grad_scattered_output):\n    gathered_input, weight = ctx.saved_tensors\n    (\n        grad_gathered_input,\n        grad_weight,\n    ) = sequence_parallel_trailing_matmul_bwd(\n        gathered_input,\n        weight,\n        grad_scattered_output,\n        ctx.fuse,\n        ctx.process_group_name,\n    )\n    return grad_gathered_input, grad_weight, None, None\n\n\ntorch.library.register_autograd(\n    \"xformers_python::sequence_parallel_trailing_matmul_fwd\",\n    sequence_parallel_trailing_matmul_bwd_bridge,\n    setup_context=sequence_parallel_trailing_matmul_setup_context,\n)\n\n\ndef sequence_parallel_trailing_matmul(\n    x: torch.Tensor,\n    w: torch.Tensor,\n    *,\n    fuse: bool,\n    process_group: torch.distributed.ProcessGroup,\n) -> torch.Tensor:\n    o = sequence_parallel_trailing_matmul_fwd(\n        x.flatten(0, -2), w, fuse, process_group.group_name\n    )\n    return o.view(-1, *x.shape[1:-1], w.shape[1])\n"
  },
  {
    "path": "xformers/ops/sequence_parallel_fused_ops.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport os\nfrom typing import Callable, Dict, List, Optional, overload, Sequence, Union\n\nimport torch\nimport torch.distributed as dist\nimport torch.multiprocessing.reductions\nfrom torch.distributed._symmetric_memory import get_symm_mem_workspace\n\nOP_FINISHED_CHANNEL = 0\nCOMMS_READY_CHANNEL = 1\n\nMS_IN_S = 1_000\n\n\ndef _is_fp8_dtype(dt: torch.dtype):\n    # Detect if it's float8_e4m3fn or float8_e5m2 without mentioning them in\n    # order to support old versions of PyTorch that don't define them.\n    return dt.is_floating_point and torch.finfo(dt).bits == 8\n\n\nclass _FusedSequenceParallel:\n    \"\"\"Set up a communication ring and perform fused ops on it\n\n    Stores the persistent state needed to support a ring of connections between\n    processes, and the logic that can do fused comms + matmuls on it.\n\n    We want to achieve overlap between:\n    - a computation which reads from the data we received from a remote GPU\n    - and the communication where we send some data to another GPU\n    And in order to do that we need some staging buffers and a way to\n    synchronize access to them across processes.\n\n    To perform the communication over NVLink we make the processes exchange\n    their staging buffers using IPC (Inter-Process Communication) handles, which\n    \"mounts\"/\"mmaps\" an allocation on one GPU into the virtual address space of\n    another GPU: the memory remains backed by the original GPU but the other GPU\n    can access it as if it were local. We exchange these IPC handles using\n    multiprocessing Connections (and the \"reductions\" provided by PyTorch),\n    which we establish over UNIX domain sockets, whose addresses we exchange by\n    using a ProcessGroup.\n\n    To synchronize accesses we use a set of counters/sequence numbers that are\n    also allocated in memory shared over IPC handles. Processes signal that they\n    completed an operation by launching a kernel that increases that value, and\n    they wait for anoher process to complete an operation by launching a kernel\n    that busy-waits for that value to increase. Currently we implement these\n    kernels manually, but on recent CUDA drivers (515.43.04+, corresponding to\n    CUDA 11.7) we could use standard stream memory operations (see\n    https://docs.nvidia.com/cuda/archive/11.7.0/cuda-driver-api/group__CUDA__MEMOP.html).\n\n    We prefer to use these kernels (or the stream memory ops) over IPC events\n    because IPC events require signaling between processes at launch time to\n    ensure that the wait on one process occurs after the record on another\n    process. This signaling means that _launching_ our fused operation becomes a\n    synchronization barrier, which can increase the launch overhead. It would\n    also behave differently from NCCL, where launching is async and all the\n    synchronization happens on device in the kernels. A previous version of this\n    code which uses IPC events can be found here:\n    https://github.com/fairinternal/xformers/pull/504.\n\n    \"\"\"\n\n    def __init__(\n        self,\n        device: torch.device,\n        group: dist.ProcessGroup,\n    ):\n        self.my_device = device\n        self.my_rank = group.rank()\n        self.world_size = group.size()\n        self.group = group\n\n        self.second_stream = torch.cuda.Stream()\n        # CUDA can schedule the matmul and the memcpy at the same time, but it\n        # tends to run the matmul first and delay the memcpy, which causes a\n        # domino effect. We thus \"encourage\" it to prioritize the memcpy.\n        self.memcpy_stream = torch.cuda.Stream(priority=-1)\n        # Use dedicated streams to run the wait kernels in the background.\n        self.compute_wait_stream = torch.cuda.Stream(priority=-1)\n        self.memcpy_wait_stream = torch.cuda.Stream(priority=-1)\n\n        self.next_stream_idx = 0\n\n    def make_stream_factory(\n        self, current_stream: torch.cuda.Stream\n    ) -> Callable[[], torch.cuda.Stream]:\n        def result():\n            stream = [current_stream, self.second_stream][self.next_stream_idx]\n            self.next_stream_idx += 1\n            self.next_stream_idx %= 2\n            return stream\n\n        return result\n\n    def allgather_and_linear(\n        self,\n        scattered_inputs: List[torch.Tensor],\n        my_matmul: Callable[\n            [List[torch.Tensor], int, Callable[[], torch.cuda.Stream]], None\n        ],\n        timeout_s: int,\n        _wait: bool = True,\n        _memcpy: bool = True,\n    ):\n        \"\"\"Perform a fused all-gather followed by a linear layer\"\"\"\n\n        dtype = scattered_inputs[0].dtype\n        assert all(si.device == self.my_device for si in scattered_inputs)\n        assert all(si.dtype == dtype for si in scattered_inputs)\n\n        scattered_input_numels = [si.numel() for si in scattered_inputs]\n        total_scattered_input_numel = sum(scattered_input_numels)\n\n        with torch.cuda.device(self.my_device):\n            symm_mem = get_symm_mem_workspace(\n                self.group.group_name,\n                self.world_size * total_scattered_input_numel * dtype.itemsize,\n            )\n            # FIXME Do something about random_init if _memcpy is True.\n            buffers = [\n                [\n                    s.view((self.world_size,) + si.shape)\n                    for s, si in zip(\n                        symm_mem.get_buffer(\n                            rank, [self.world_size, total_scattered_input_numel], dtype\n                        ).split(scattered_input_numels, dim=-1),\n                        scattered_inputs,\n                    )\n                ]\n                for rank in range(self.world_size)\n            ]\n\n        current_stream = torch.cuda.current_stream()\n\n        # Signal to buddy that we have read from the data (in previous iter) so\n        # it can overwrite it (this write matches up with wait [B] below).\n        for iter_ in range(1, self.world_size):\n            src_rank = (self.my_rank - iter_) % self.world_size\n            if _wait:\n                with torch.cuda.stream(current_stream):\n                    symm_mem.put_signal(src_rank, OP_FINISHED_CHANNEL)\n\n        self.second_stream.wait_stream(current_stream)\n        self.compute_wait_stream.wait_stream(current_stream)\n        self.memcpy_wait_stream.wait_stream(current_stream)\n        stream_factory = self.make_stream_factory(current_stream)\n\n        for iter_ in range(1, self.world_size):\n            dst_rank = (self.my_rank + iter_) % self.world_size\n\n            # Wait for buddy to signal that it read from the data before we\n            # overwrite it (this wait matches up with write [B] above).\n            if _wait:\n                with torch.cuda.stream(self.memcpy_wait_stream):\n                    symm_mem.wait_signal(\n                        dst_rank,\n                        OP_FINISHED_CHANNEL,\n                        timeout_ms=timeout_s * MS_IN_S,  # type: ignore[call-arg]\n                    )\n\n            self.memcpy_stream.wait_stream(self.memcpy_wait_stream)\n\n            if _memcpy:\n                with torch.cuda.stream(self.memcpy_stream):\n                    for bs, si in zip(buffers[dst_rank], scattered_inputs):\n                        bs[self.my_rank].copy_(si)\n\n            # Signal to buddy that we have written into the data so it can\n            # read from it (this write matches up with wait [A] below).\n            if _wait:\n                with torch.cuda.stream(self.memcpy_stream):\n                    symm_mem.memset32(\n                        symm_mem.get_signal_pad(dst_rank),  # type: ignore[attr-defined]\n                        self.world_size * COMMS_READY_CHANNEL + self.my_rank,\n                        val=1,\n                        count=1,\n                    )\n\n        my_matmul(scattered_inputs, self.my_rank, stream_factory)\n\n        for iter_ in range(1, self.world_size):\n            src_rank = (self.my_rank - iter_) % self.world_size\n\n            # Wait for buddy to signal that it wrote into the data before we\n            # read from it (this wait matches up with write [A] above).\n            if _wait:\n                with torch.cuda.stream(self.compute_wait_stream):\n                    symm_mem.wait_signal(\n                        src_rank,\n                        COMMS_READY_CHANNEL,\n                        timeout_ms=timeout_s * MS_IN_S,  # type: ignore[call-arg]\n                    )\n\n            current_stream.wait_stream(self.compute_wait_stream)\n            self.second_stream.wait_stream(self.compute_wait_stream)\n\n            my_matmul(\n                [s[src_rank] for s in buffers[self.my_rank]], src_rank, stream_factory\n            )\n\n        current_stream.wait_stream(self.second_stream)\n        current_stream.wait_stream(self.memcpy_stream)\n\n    def linear_and_reducescatter(\n        self,\n        my_matmul: Callable[\n            [List[torch.Tensor], int, Callable[[], torch.cuda.Stream]], None\n        ],\n        gathered_outputs: List[torch.Tensor],\n        scattered_outputs: List[torch.Tensor],\n        timeout_s: int,\n        _wait: bool = True,\n        _memcpy: bool = True,\n    ):\n        \"\"\"Perform a fused linear layer followed by a reduce-scatter\"\"\"\n\n        dtype = gathered_outputs[0].dtype\n        assert all(go.device == self.my_device for go in gathered_outputs)\n        assert all(go.dtype == dtype for go in gathered_outputs)\n        assert all(so.device == self.my_device for so in scattered_outputs)\n        assert all(so.dtype == dtype for so in scattered_outputs)\n\n        scattered_output_numels = [so.numel() for so in scattered_outputs]\n        total_scattered_output_numel = sum(scattered_output_numels)\n\n        with torch.cuda.device(self.my_device):\n            symm_mem = get_symm_mem_workspace(\n                self.group.group_name,\n                self.world_size * total_scattered_output_numel * dtype.itemsize,\n            )\n            # FIXME Do something about random_init if _memcpy is True.\n            buffers = [\n                [\n                    s.view((self.world_size,) + so.shape)\n                    for s, so in zip(\n                        symm_mem.get_buffer(\n                            rank, [self.world_size, total_scattered_output_numel], dtype\n                        ).split(scattered_output_numels, dim=-1),\n                        scattered_outputs,\n                    )\n                ]\n                for rank in range(self.world_size)\n            ]\n\n        current_stream = torch.cuda.current_stream()\n\n        # Signal to buddy that we have read from the data (in previous iter)\n        # so it can overwrite it (this write matches up with wait [2] below).\n        for iter_ in range(1, self.world_size):\n            src_rank = (self.my_rank - iter_) % self.world_size\n            if _wait:\n                with torch.cuda.stream(current_stream):\n                    symm_mem.put_signal(src_rank, OP_FINISHED_CHANNEL)\n\n        self.second_stream.wait_stream(current_stream)\n        self.compute_wait_stream.wait_stream(current_stream)\n        self.memcpy_wait_stream.wait_stream(current_stream)\n        stream_factory = self.make_stream_factory(current_stream)\n\n        for iter_ in range(1, self.world_size):\n            dst_rank = (self.my_rank + iter_) % self.world_size\n\n            # Wait for buddy to signal that it read from the data before we\n            # overwrite it (this wait matches up with write [2] above).\n            if _wait:\n                with torch.cuda.stream(self.compute_wait_stream):\n                    symm_mem.wait_signal(\n                        dst_rank,\n                        OP_FINISHED_CHANNEL,\n                        timeout_ms=timeout_s * MS_IN_S,  # type: ignore[call-arg]\n                    )\n\n            current_stream.wait_stream(self.compute_wait_stream)\n            self.second_stream.wait_stream(self.compute_wait_stream)\n\n            my_matmul(\n                [s[dst_rank] for s in buffers[self.my_rank]], dst_rank, stream_factory\n            )\n\n            # Deduce which stream contains the last kernel launched.\n            final_stream = [current_stream, self.second_stream][\n                (self.next_stream_idx - 1) % 2\n            ]\n            final_stream.wait_stream(current_stream)\n            final_stream.wait_stream(self.second_stream)\n\n            # Signal to buddy that we have written into the data so it can\n            # read from it (this write matches up with wait [1] below).\n            if _wait:\n                with torch.cuda.stream(final_stream):\n                    symm_mem.memset32(\n                        symm_mem.get_signal_pad(dst_rank),  # type: ignore[attr-defined]\n                        self.world_size * COMMS_READY_CHANNEL + self.my_rank,\n                        val=1,\n                        count=1,\n                    )\n\n        my_matmul(\n            [o[self.my_rank] for o in gathered_outputs],\n            self.my_rank,\n            stream_factory,\n        )\n\n        for iter_ in range(1, self.world_size):\n            src_rank = (self.my_rank - iter_) % self.world_size\n\n            # Wait for buddy to signal that it wrote into the data before we\n            # read from it (this wait matches up with write [1] above).\n            if _wait:\n                with torch.cuda.stream(self.memcpy_wait_stream):\n                    symm_mem.wait_signal(\n                        src_rank,\n                        COMMS_READY_CHANNEL,\n                        timeout_ms=timeout_s * MS_IN_S,  # type: ignore[call-arg]\n                    )\n\n            self.memcpy_stream.wait_stream(self.memcpy_wait_stream)\n\n            if _memcpy:\n                with torch.cuda.stream(self.memcpy_stream):\n                    for go, bs in zip(gathered_outputs, buffers[src_rank]):\n                        go[src_rank].copy_(bs[self.my_rank])\n\n        current_stream.wait_stream(self.second_stream)\n        current_stream.wait_stream(self.memcpy_stream)\n\n        for go, so in zip(gathered_outputs, scattered_outputs):\n            torch.sum(go, dim=0, out=so)\n\n\n# We'd store this as an attribute on the PG object itself, but some PGs are\n# pybind-bound classes and thus don't support it, so we simulate this as an\n# external cache.\nCACHE: Dict[int, Optional[_FusedSequenceParallel]] = {}\n\n\ndef _can_ranks_communicate_all_to_all_over_nvlink(group: dist.ProcessGroup) -> bool:\n    # FIXME This is currently overly simplistic, must be improved. The following\n    # should be enough:\n    # - ensure that all ranks are running on the same machine (by exchanging\n    #   their /proc/sys/kernel/random/boot_id value)\n    # - ensure there's P2P between all pairs of ranks (can_device_access_peer\n    #   could help here but it's unclear what happens if target devices aren't\n    #   visible? maybe just trying to exchange IPC handles and catching errors\n    #   would work? note that in any case some ranks might succeed while some\n    #   might fail so we need a barrier to have them all make the same decision)\n    return group.size() <= 8\n\n\ndef _lazy_init(\n    device: torch.device, group: dist.ProcessGroup\n) -> Optional[_FusedSequenceParallel]:\n    world_size = group.size()\n    try:\n        obj = CACHE[id(group)]\n    except KeyError:\n        if int(os.environ.get(\"DISABLE_FUSED_SEQUENCE_PARALLEL\", \"0\")):\n            obj = None\n        elif world_size == 1:\n            obj = None\n        elif not _can_ranks_communicate_all_to_all_over_nvlink(group):\n            obj = None\n        else:\n            obj = _FusedSequenceParallel(device, group)\n        CACHE[id(group)] = obj\n    return obj\n\n\ndef _default_stream_factory() -> torch.cuda.Stream:\n    return torch.cuda.current_stream()\n\n\n@overload\ndef fused_allgather_and_linear(\n    scattered_input: torch.Tensor,\n    weight: torch.Tensor,\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[torch.Tensor] = None,\n    timeout_s: int = 60 * 60,\n    scale_scattered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> torch.Tensor: ...\n\n\n@overload\ndef fused_allgather_and_linear(\n    scattered_input: torch.Tensor,\n    weight: List[torch.Tensor],\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[List[torch.Tensor]] = None,\n    timeout_s: int = 60 * 60,\n    scale_scattered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> List[torch.Tensor]: ...\n\n\ndef fused_allgather_and_linear(\n    scattered_input: torch.Tensor,\n    weight: Union[torch.Tensor, List[torch.Tensor]],\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    timeout_s: int = 60 * 60,\n    scale_scattered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> Union[torch.Tensor, List[torch.Tensor]]:\n    \"\"\"Performs a fused all-gather followed by a linear op\n\n    It is equivalent to the following plain PyTorch code:\n\n    # like scattered_input but with first dim multiplied by group's world size\n    gathered_input = scattered_input.new_empty(...)\n    dist.all_gather_into_tensor(gathered_input, scattered_input, group=group)\n    return torch.nn.functional.linear(gathered_input, weight)\n\n    It achieves this by breaking down the matmul into smaller partial ops (as\n    many as the world size), each needing as input a different \"contribution\"\n    to the all-gather (by a different rank), and writing to a different chunk of\n    the output. Then, on one stream, it sends the local contribution to all\n    other ranks (first one rank over, then two, ...) while, on another stream,\n    it launches the sub-matmuls in the order in which the remote contributions\n    (which are the sub-matmuls' inputs) are supposed to arrive, so that ideally\n    none of the sub-matmuls will ever have to wait.\n\n    The idea comes from this paper: https://arxiv.org/abs/2302.05442\n\n    This method uses a staging buffer, which persists across calls, of the same\n    size as the all-gathered input tensor (i.e., the input's size times the\n    world size). If multiple inputs of multiple sizes are used, the staging\n    buffer will be the maximum needed by any of them. Each call, when it starts,\n    must first wait for the previous call to finish using the staging buffer. In\n    normal conditions, where there's some other operation between two calls,\n    this isn't an issue.\n\n    Supports FP8 gemm for tensor-wise quantized weight and input tensors.\n    To enable FP8 gemm:\n    1. pass scattered_input and weight as quantized FP8 datatype\n    2. pass scale_scattered_input and scale_weight, the scales used to\n    quantize input and weight, respectively.\n    3. set out_dtype, if not specified, will be inferred from scattered_input type.\n\n    \"\"\"\n    world_size = group.size()\n    weights = weight if isinstance(weight, list) else [weight]\n    assert (scale_scattered_input is None) == (scale_weight is None)\n    if scale_weight is not None:\n        assert isinstance(weight, list) == isinstance(scale_weight, list)\n        scales_weights: Sequence[Optional[torch.Tensor]] = (\n            scale_weight if isinstance(scale_weight, list) else [scale_weight]\n        )\n        assert len(weights) == len(scales_weights)\n        assert _is_fp8_dtype(scattered_input.dtype)\n        assert all(_is_fp8_dtype(w.dtype) for w in weights)\n        assert out_dtype is not None, \"output_dtype is required with FP8\"\n    else:\n        scales_weights = [None] * len(weights)\n    assert all(w.ndim == 2 for w in weights)\n    assert scattered_input.ndim >= 2\n    assert all(scattered_input.shape[-1] == w.shape[-1] for w in weights)\n    assert scattered_input.is_contiguous()\n    gathered_input_shape = (world_size,) + scattered_input.shape\n    gathered_output_shapes = [gathered_input_shape[:-1] + w.shape[:-1] for w in weights]\n    if out is not None:\n        assert isinstance(out, list) == isinstance(weight, list)\n        gathered_outputs = out if isinstance(out, list) else [out]\n        assert len(gathered_outputs) == len(gathered_output_shapes)\n        assert all(\n            go.shape == gos for go, gos in zip(gathered_outputs, gathered_output_shapes)\n        )\n        assert all(go.is_contiguous() for go in gathered_outputs)\n        if out_dtype is not None:\n            if isinstance(out, list):\n                for o in out:\n                    assert o.dtype == out_dtype\n            else:\n                assert out.dtype == out_dtype\n    else:\n        gathered_outputs = [\n            scattered_input.new_empty(\n                gos,\n                dtype=out_dtype if out_dtype is not None else scattered_input.dtype,\n            )\n            for gos in gathered_output_shapes\n        ]\n\n    torch.ops.xformers_python._fused_allgather_and_linear_impl(\n        scattered_input,\n        weights,\n        group.group_name,\n        gathered_outputs,\n        timeout_s=timeout_s,\n        _wait=private_args_DO_NOT_USE.get(\"_wait\", True),\n        _memcpy=private_args_DO_NOT_USE.get(\"_memcpy\", True),\n        scale_scattered_input=scale_scattered_input,\n        scales_weights=scales_weights,\n    )\n\n    if isinstance(weight, list):\n        return [go.flatten(0, 1) for go in gathered_outputs]\n    else:\n        return gathered_outputs[0].flatten(0, 1)\n\n\n@torch.library.custom_op(\n    \"xformers_python::_fused_allgather_and_linear_impl\",\n    mutates_args={\"gathered_outputs\"},\n    device_types=\"cuda\",\n)\ndef _fused_allgather_and_linear_custom_op(\n    scattered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    process_group_name: dist.distributed_c10d.GroupName,\n    gathered_outputs: List[torch.Tensor],\n    timeout_s: int,\n    _wait: bool,\n    _memcpy: bool,\n    scale_scattered_input: torch.Tensor,\n    scales_weights: Sequence[Optional[torch.Tensor]],\n) -> None:\n    process_group = dist.distributed_c10d._resolve_process_group(process_group_name)\n\n    def my_matmul(\n        inputs: List[torch.Tensor],\n        src_rank: int,\n        stream_factory: Callable[[], torch.cuda.Stream],\n    ) -> None:\n        for w, scale_weight, go in zip(weights, scales_weights, gathered_outputs):\n            with torch.cuda.stream(stream_factory()):\n                if scale_scattered_input is not None and scale_weight is not None:\n                    torch._scaled_mm(\n                        inputs[0],\n                        w.t(),\n                        out_dtype=go[src_rank].dtype,\n                        scale_a=scale_scattered_input,\n                        scale_b=scale_weight,\n                        out=go[src_rank],\n                    )\n                else:\n                    torch.matmul(inputs[0], w.t(), out=go[src_rank])\n\n    fused_allgather_and_anything(\n        [scattered_input],\n        my_matmul,\n        group=process_group,\n        timeout_s=timeout_s,\n        _wait=_wait,\n        _memcpy=_memcpy,\n    )\n\n\ndef fused_allgather_and_anything(\n    scattered_inputs: List[torch.Tensor],\n    my_matmul: Callable[\n        [List[torch.Tensor], int, Callable[[], torch.cuda.Stream]], None\n    ],\n    *,\n    group: dist.ProcessGroup,\n    timeout_s: int = 60 * 60,\n    **private_args_DO_NOT_USE,\n) -> None:\n    world_size = group.size()\n\n    if len(scattered_inputs) == 0:\n        for src_rank in range(world_size):\n            my_matmul([], src_rank, _default_stream_factory)\n        return\n\n    assert all(si.is_contiguous() for si in scattered_inputs)\n    assert all(si.device == scattered_inputs[0].device for si in scattered_inputs)\n    assert all(si.dtype == scattered_inputs[0].dtype for si in scattered_inputs)\n\n    gathered_input_shapes = [(world_size,) + si.shape for si in scattered_inputs]\n\n    obj = _lazy_init(scattered_inputs[0].device, group)\n\n    if world_size == 1:\n        my_matmul(scattered_inputs, 0, _default_stream_factory)\n\n    # Fallback\n    elif obj is None:\n        gathered_inputs = [\n            si.new_empty(gis)\n            for si, gis in zip(scattered_inputs, gathered_input_shapes)\n        ]\n        for si, gi in zip(scattered_inputs, gathered_inputs):\n            dist.all_gather_into_tensor(output_tensor=gi, input_tensor=si, group=group)\n        for src_rank in range(world_size):\n            my_matmul(\n                [gi[src_rank] for gi in gathered_inputs],\n                src_rank,\n                _default_stream_factory,\n            )\n\n    # Fast path\n    else:\n        assert scattered_inputs[0].device == obj.my_device\n        obj.allgather_and_linear(\n            scattered_inputs,\n            my_matmul,\n            timeout_s=timeout_s,\n            _wait=private_args_DO_NOT_USE.get(\"_wait\", True),\n            _memcpy=private_args_DO_NOT_USE.get(\"_memcpy\", True),\n        )\n\n\n@overload\ndef fused_linear_and_reducescatter(\n    gathered_input: torch.Tensor,\n    weight: torch.Tensor,\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[torch.Tensor] = None,\n    timeout_s: int = 60 * 60,\n    scale_gathered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> torch.Tensor: ...\n\n\n@overload\ndef fused_linear_and_reducescatter(\n    gathered_input: torch.Tensor,\n    weight: List[torch.Tensor],\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[List[torch.Tensor]] = None,\n    timeout_s: int = 60 * 60,\n    scale_gathered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> List[torch.Tensor]: ...\n\n\ndef fused_linear_and_reducescatter(\n    gathered_input: torch.Tensor,\n    weight: Union[torch.Tensor, List[torch.Tensor]],\n    *,\n    group: dist.ProcessGroup,\n    out: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    timeout_s: int = 60 * 60,\n    scale_gathered_input: Optional[torch.Tensor] = None,\n    scale_weight: Optional[Union[torch.Tensor, List[torch.Tensor]]] = None,\n    out_dtype: Optional[torch.dtype] = None,\n    **private_args_DO_NOT_USE,\n) -> Union[torch.Tensor, List[torch.Tensor]]:\n    \"\"\"Performs a fused linear op followed by a reduce-scatter\n\n    It is equivalent to the following plain PyTorch code:\n\n    gathered_output = torch.nn.functional.linear(gathered_input, weight)\n    # like gathered_output but with first dim divided by group's world size\n    scattered_output = gathered_output.new_empty(...)\n    dist.reduce_scatter_tensor(scattered_output, gathered_output, group=group)\n\n    Supports FP8 gemm with tensor-wise quantized weights. To enable FP8 gemm:\n    1. pass weight and gathered_input as FP8 tensors\n    2. Set `scale_gathered_input` and `scale_weight` to the scales used to quantize\n    inputs and weight, respectively.\n    3. Set out_dtype to the desired output dtype. If not specified, it will be inferred from\n    gathered_input datatype.\n    \"\"\"\n    world_size = group.size()\n    weights = weight if isinstance(weight, list) else [weight]\n    assert (scale_gathered_input is None) == (scale_weight is None)\n    if scale_weight is not None:\n        assert isinstance(weight, list) == isinstance(scale_weight, list)\n        scales_weights: Sequence[Optional[torch.Tensor]] = (\n            scale_weight if isinstance(scale_weight, list) else [scale_weight]\n        )\n        assert len(weights) == len(scales_weights)\n        assert _is_fp8_dtype(gathered_input.dtype)\n        assert all(_is_fp8_dtype(w.dtype) for w in weights)\n        assert out_dtype is not None, \"output_dtype is required with FP8\"\n    else:\n        scales_weights = [None] * len(weights)\n    assert all(w.ndim == 2 for w in weights)\n    assert gathered_input.ndim >= 2\n    assert all(gathered_input.shape[-1] == w.shape[-1] for w in weights)\n    assert gathered_input.is_contiguous()\n    assert gathered_input.shape[0] % world_size == 0\n    gathered_input = gathered_input.view(\n        (world_size, gathered_input.shape[0] // world_size) + gathered_input.shape[1:]\n    )\n    gathered_output_shapes = [gathered_input.shape[:-1] + w.shape[:-1] for w in weights]\n    scattered_output_shapes = [gos[1:] for gos in gathered_output_shapes]\n    if out is not None:\n        assert isinstance(out, list) == isinstance(weight, list)\n        scattered_outputs = out if isinstance(out, list) else [out]\n        assert len(scattered_outputs) == scattered_output_shapes\n        assert all(so.device == gathered_input.device for so in scattered_outputs)\n        assert all(so.dtype == gathered_input.dtype for so in scattered_outputs)\n        assert all(\n            so.shape == sos\n            for so, sos in zip(scattered_outputs, scattered_output_shapes)\n        )\n        if out_dtype is not None:\n            if isinstance(out, list):\n                for o in out:\n                    assert o.dtype == out_dtype\n            else:\n                assert out.dtype == out_dtype\n    else:\n        scattered_outputs = [\n            gathered_input.new_empty(\n                sos,\n                dtype=out_dtype if out_dtype is not None else gathered_input.dtype,\n            )\n            for sos in scattered_output_shapes\n        ]\n\n    torch.ops.xformers_python._fused_linear_and_reducescatter_impl(\n        gathered_input,\n        weights,\n        group.group_name,\n        scattered_outputs,\n        timeout_s=timeout_s,\n        _wait=private_args_DO_NOT_USE.get(\"_wait\", True),\n        _memcpy=private_args_DO_NOT_USE.get(\"_memcpy\", True),\n        scale_gathered_input=scale_gathered_input,\n        scales_weights=scales_weights,\n    )\n\n    if isinstance(weight, list):\n        return scattered_outputs\n    else:\n        return scattered_outputs[0]\n\n\n@torch.library.custom_op(\n    \"xformers_python::_fused_linear_and_reducescatter_impl\",\n    mutates_args={\"scattered_outputs\"},\n    device_types=\"cuda\",\n)\ndef _fused_linear_and_reducescatter_custom_op(\n    gathered_input: torch.Tensor,\n    weights: List[torch.Tensor],\n    process_group_name: dist.distributed_c10d.GroupName,\n    scattered_outputs: List[torch.Tensor],\n    timeout_s: int,\n    _wait: bool,\n    _memcpy: bool,\n    scale_gathered_input: torch.Tensor,\n    scales_weights: Sequence[Optional[torch.Tensor]],\n) -> None:\n    process_group = dist.distributed_c10d._resolve_process_group(process_group_name)\n\n    def my_matmul(\n        outputs: List[torch.Tensor],\n        dst_rank: int,\n        stream_factory: Callable[[], torch.cuda.Stream],\n    ) -> None:\n        for w, scale_weight, o in zip(weights, scales_weights, outputs):\n            with torch.cuda.stream(stream_factory()):\n                if scale_gathered_input is not None and scale_weight is not None:\n                    torch._scaled_mm(\n                        gathered_input[dst_rank],\n                        w.t(),\n                        out_dtype=o.dtype,\n                        scale_a=scale_gathered_input,\n                        scale_b=scale_weight,\n                        out=o,\n                    )\n                else:\n                    torch.matmul(gathered_input[dst_rank], w.t(), out=o)\n\n    fused_anything_and_reducescatter(\n        my_matmul,\n        scattered_outputs,\n        group=process_group,\n        timeout_s=timeout_s,\n        _wait=_wait,\n        _memcpy=_memcpy,\n    )\n\n\ndef fused_anything_and_reducescatter(\n    my_matmul: Callable[\n        [List[torch.Tensor], int, Callable[[], torch.cuda.Stream]], None\n    ],\n    scattered_outputs: List[torch.Tensor],\n    *,\n    group: dist.ProcessGroup,\n    timeout_s: int = 60 * 60,\n    **private_args_DO_NOT_USE,\n) -> None:\n    world_size = group.size()\n\n    if len(scattered_outputs) == 0:\n        for dst_rank in range(world_size):\n            my_matmul([], dst_rank, _default_stream_factory)\n        return\n\n    assert all(so.is_contiguous() for so in scattered_outputs)\n    assert all(so.device == scattered_outputs[0].device for so in scattered_outputs)\n    assert all(so.dtype == scattered_outputs[0].dtype for so in scattered_outputs)\n\n    gathered_output_shapes = [(world_size,) + so.shape for so in scattered_outputs]\n\n    obj = _lazy_init(scattered_outputs[0].device, group)\n\n    if world_size == 1:\n        my_matmul(scattered_outputs, 0, _default_stream_factory)\n\n    # Fallback\n    elif obj is None:\n        gathered_outputs = [\n            so.new_empty(gos)\n            for so, gos in zip(scattered_outputs, gathered_output_shapes)\n        ]\n        for dst_rank in range(world_size):\n            my_matmul(\n                [go[dst_rank] for go in gathered_outputs],\n                dst_rank,\n                _default_stream_factory,\n            )\n        for go, so in zip(gathered_outputs, scattered_outputs):\n            dist.reduce_scatter_tensor(output=so, input=go, group=group)\n\n    # Fast path\n    else:\n        assert scattered_outputs[0].device == obj.my_device\n        gathered_outputs = [\n            scattered_outputs[0].new_empty(gos) for gos in gathered_output_shapes\n        ]\n        obj.linear_and_reducescatter(\n            my_matmul,\n            gathered_outputs,\n            scattered_outputs,\n            timeout_s=timeout_s,\n            _wait=private_args_DO_NOT_USE.get(\"_wait\", True),\n            _memcpy=private_args_DO_NOT_USE.get(\"_memcpy\", True),\n        )\n"
  },
  {
    "path": "xformers/ops/sp24.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport contextlib\nimport os\nimport time\nfrom functools import partial\nfrom typing import Any, Callable, cast, Dict, List, Optional, Tuple, TypeVar\n\nimport torch\n\nfrom .common import BaseOperator, get_operator, get_xformers_operator, register_operator\n\n\n@register_operator\nclass SparsifyBothWays(BaseOperator):\n    OPERATOR = get_xformers_operator(\"sparse24_sparsify_both_ways\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = \"sparse24_sparsify_both_ways\"\n\n\n@register_operator\nclass SparsifyApply(BaseOperator):\n    OPERATOR = get_xformers_operator(\"sparse24_apply\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = \"sparse24_apply\"\n\n\n@register_operator\nclass SparsifyApplyDenseOutput(BaseOperator):\n    OPERATOR = get_xformers_operator(\"sparse24_apply_dense_output\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = \"sparse24_apply_dense_output\"\n\n\n@register_operator\nclass Sp24Gemm(BaseOperator):\n    OPERATOR = get_xformers_operator(\"_sparse24_gemm\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = \"_sparse24_gemm\"\n\n\ndef _get_cusparselt_torch_version() -> Tuple[int, int, int]:\n    \"\"\"\n    Returns the version of the cusparselt.so library used by pytorch\n    \"\"\"\n    if not torch.backends.cusparselt.is_available():\n        return (0, 0, 0)\n    version: Optional[int] = torch.backends.cusparselt.version()\n    if version is None:\n        return (0, 0, 0)\n    return ((version // 10000) % 100, (version // 100) % 100, version % 100)\n\n\n_cusplt_version = _get_cusparselt_torch_version()\n_cusplt_version_str = \".\".join(str(v) for v in _cusplt_version)\n\n\n@register_operator\nclass Sp24GemmCuspltSearch(BaseOperator):\n    OPERATOR = get_operator(\"aten\", \"_cslt_sparse_mm_search\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = f\"_cslt_sparse_mm_search@{_cusplt_version_str}\"\n\n\n@register_operator\nclass Sp24GemmCusplt(BaseOperator):\n    OPERATOR = get_operator(\"aten\", \"_cslt_sparse_mm\")\n    OPERATOR_CATEGORY = \"sp24\"\n    NAME = f\"_cslt_sparse_mm@{_cusplt_version_str}\"\n\n\ndef _has_cusparseLt() -> bool:\n    available = _cusplt_version >= (0, 5, 0)\n    if not available:\n        return False\n\n    # Sm90 added in 6.0\n    compute_capability = (0, 0)\n    if torch.cuda.is_available():\n        compute_capability = torch.cuda.get_device_capability(\"cuda\")\n    if _cusplt_version < (6, 0, 0):\n        if compute_capability >= (9, 0):\n            return False\n    return available\n\n\ndef sparse24_pointwise_op(\n    func, types, args=(), kwargs=None, allow_sparsify_args_list=()\n):\n    self = None\n    for tensor in args:\n        if isinstance(tensor, Sparse24Tensor):\n            self = tensor\n    assert self is not None\n    args_updated = []\n    for i, tensor in enumerate(args):\n        if isinstance(tensor, torch.Tensor):\n            if not isinstance(tensor, Sparse24Tensor):\n                if i in allow_sparsify_args_list:\n                    tensor = sparsify24_like(tensor, self)\n                else:\n                    raise ValueError(\n                        f\"Operation {func.__module__}.{func.__name__} on Sparse24Tensor requires all operands to \"\n                        f\"be Sparse24Tensors, but operand {i} is a {type(tensor)}\"\n                    )\n            if (\n                tensor.threads_masks is None\n                or self.threads_masks is None\n                or tensor.threads_masks.data_ptr() != self.threads_masks.data_ptr()\n                or tensor.threads_masks.stride() != self.threads_masks.stride()\n            ):\n                raise ValueError(\n                    f\"Operation {func.__module__}.{func.__name__} on Sparse24Tensor requires all operands to be \"\n                    \"Sparse24Tensors with the same sparsity pattern\"\n                )\n        args_updated.append(tensor)\n    assert isinstance(\n        self, Sparse24TensorCutlass\n    ), \"Only implemented for CUTLASS tensors\"\n    return Sparse24TensorCutlass(\n        self.shape,\n        func(\n            *[(x.packed if isinstance(x, Sparse24Tensor) else x) for x in args_updated]\n        ),\n        self.meta,\n        func(\n            *[\n                (x.packed_t if isinstance(x, Sparse24Tensor) else x)\n                for x in args_updated\n            ]\n        ),\n        self.meta_t,\n        self.threads_masks,\n    )\n\n\ndef sparse24_mm(func, types, args=(), kwargs=None) -> torch.Tensor:\n    assert len(args) == 2\n    A, B = args\n    if A.ndim != 2 or B.ndim != 2:\n        raise NotImplementedError(\n            \"`Sparse24Tensor` matmul: Broadcasting is not implemented\"\n        )\n    if isinstance(A, Sparse24Tensor):\n        return A._mm(B)\n    else:\n        B_t = B.t()\n        assert isinstance(B_t, Sparse24Tensor)\n        return B_t._mm(A.t(), prefer_col_major_output=True).t()\n\n\ndef sparse24_addmm(func, types, args=(), kwargs=None) -> torch.Tensor:\n    assert len(args) == 3\n    bias, A, B = args\n    if A.ndim != 2 or B.ndim != 2:\n        raise NotImplementedError(\n            \"`Sparse24Tensor` matmul: Broadcasting is not implemented\"\n        )\n    if bias.ndim != 1:\n        raise NotImplementedError(\n            f\"`Sparse24Tensor` matmul: only bias dim=1 supported. Shape={bias.shape}\"\n        )\n    if isinstance(A, Sparse24Tensor):\n        raise NotImplementedError(\n            \"`Sparse24Tensor` matmul: only operand B of `addmm` can be sparse\"\n        )\n    B_t = B.t()\n    assert isinstance(B_t, Sparse24Tensor)\n    return B_t._mm(A.t(), bias=bias, prefer_col_major_output=True).t()\n\n\ndef sparse24_linear(func, types, args=(), kwargs=None) -> torch.Tensor:\n    assert len(args) in [2, 3]\n    A, B = args[:2]\n    bias = args[2] if len(args) == 3 else None\n    if bias is None:\n        return A @ B.t()\n    return sparse24_addmm(\n        func=None,\n        types=None,\n        args=[bias, A, B.t()],\n    )\n\n\ndef sparse24_t(func, types, args=(), kwargs=None) -> torch.Tensor:\n    assert len(args) == 1\n    self = args[0]\n    assert isinstance(self, Sparse24Tensor)\n    assert len(self.shape) == 2\n    return self.__class__(\n        (self.shape[-1], self.shape[0]),\n        packed=self.packed_t,\n        meta=self.meta_t,\n        packed_t=self.packed,\n        meta_t=self.meta,\n        threads_masks=self.threads_masks.transpose(0, 1),\n    )\n\n\ndef sparse24_view(func, types, args=(), kwargs=None) -> torch.Tensor:\n    assert len(args) == 2\n    self, shape = args\n    if tuple(shape) != self.shape:\n        raise NotImplementedError(\n            f\"`view` is not implemented for Sparse24Tensor, except for the dummy case (shape={shape})\"\n        )\n    return self\n\n\ndef sparse24_detach(func, types, args, kwargs) -> torch.Tensor:\n    assert len(args) == 1\n    self = args[0]\n    return self.__class__(\n        shape=self.shape,\n        packed=self.packed,\n        meta=self.meta,\n        packed_t=self.packed_t,\n        meta_t=self.meta_t,\n        threads_masks=self.threads_masks,\n        requires_grad=False,\n    )\n\n\n@contextlib.contextmanager\ndef no_dispatch():\n    guard = torch._C._DisableTorchDispatch()\n    try:\n        yield\n    finally:\n        del guard\n\n\ndef fallback_dispatcher(func, types, args, kwargs):\n    with no_dispatch():\n        return func(*args)\n\n\nSPARSE24_DISPATCH_CUTLASS = {\n    torch.ops.aten.is_same_size: fallback_dispatcher,\n    torch.ops.aten.detach_: fallback_dispatcher,\n    torch.ops.aten.detach: sparse24_detach,\n    torch.ops.aten.relu: sparse24_pointwise_op,\n    torch.ops.aten.gelu: sparse24_pointwise_op,\n    torch.ops.aten.silu: sparse24_pointwise_op,\n    torch.ops.aten.mul: partial(\n        # `mul` BW in swiglu\n        sparse24_pointwise_op,\n        allow_sparsify_args_list=(\n            0,\n            1,\n        ),\n    ),\n    torch.ops.aten.add: sparse24_pointwise_op,\n    # Note: for these ops, we allow the gradient to come in as a `torch.Tensor`\n    # and we will run the sparsification right before calling the BW aten func\n    torch.ops.aten.gelu_backward: partial(\n        sparse24_pointwise_op, allow_sparsify_args_list=(0,)\n    ),\n    torch.ops.aten.silu_backward: partial(\n        sparse24_pointwise_op, allow_sparsify_args_list=(0, 1)\n    ),\n    torch.ops.aten.threshold_backward: partial(  # relu BW\n        sparse24_pointwise_op,\n        allow_sparsify_args_list=(0,),\n    ),\n    torch.ops.aten.mm: sparse24_mm,\n    torch.ops.aten.matmul: sparse24_mm,\n    torch.ops.aten.t: sparse24_t,\n    torch.ops.aten.view: sparse24_view,\n    torch.ops.aten.linear: sparse24_linear,\n}\n\nSPARSE24_DISPATCH_CUSPARSELT = {\n    torch.ops.aten.is_same_size: fallback_dispatcher,\n    torch.ops.aten.detach_: fallback_dispatcher,\n    torch.ops.aten.detach: sparse24_detach,\n    torch.ops.aten.t: sparse24_t,\n    torch.ops.aten.view: sparse24_view,\n    torch.ops.aten.mm: sparse24_mm,\n    torch.ops.aten.matmul: sparse24_mm,\n    torch.ops.aten.addmm: sparse24_addmm,\n    torch.ops.aten.linear: sparse24_linear,\n}\n\n\nclass Sparse24Tensor(torch.Tensor):\n    packed: torch.Tensor\n    meta: torch.Tensor\n    packed_t: torch.Tensor\n    meta_t: torch.Tensor\n    threads_masks: torch.Tensor\n    __slots__ = [\"packed\", \"meta\", \"packed_t\", \"meta_t\", \"threads_masks\"]\n\n    # We need to update the new method here to tell PyTorch what should be\n    # the Tensor corresponding to the wrapper object\n    @staticmethod\n    def __new__(\n        cls,\n        shape,\n        packed: torch.Tensor,\n        meta: torch.Tensor,\n        packed_t: torch.Tensor,\n        meta_t: torch.Tensor,\n        threads_masks: torch.Tensor,\n        *,\n        requires_grad=False,\n    ):\n        assert isinstance(packed, torch.Tensor)\n        tensor = torch.Tensor._make_wrapper_subclass(  # type: ignore[attr-defined]\n            cls,\n            shape,\n            device=packed.device,\n            dtype=packed.dtype,\n            requires_grad=requires_grad,\n        )\n        tensor.packed = packed\n        tensor.meta = meta\n        tensor.packed_t = packed_t\n        tensor.meta_t = meta_t\n        tensor.threads_masks = threads_masks\n        return tensor\n\n    def __repr__(self):\n        return f\"{self.__class__.__name__}(shape={self.shape})\"\n\n    def _sp24_to_dense(self) -> torch.Tensor:\n        # Multiply by identity\n        # WARN: This is not efficient at all\n        e = torch.eye(\n            self.shape[1], self.shape[1], device=self.device, dtype=self.dtype\n        )\n        return self @ e\n\n    def _mm(\n        self,\n        B: torch.Tensor,\n        *,\n        prefer_col_major_output: bool = False,\n        bias: Optional[torch.Tensor] = None,\n    ) -> torch.Tensor:\n        raise NotImplementedError()\n\n    __torch_function__ = torch._C._disabled_torch_function_impl\n\n    def __tensor_flatten__(self):\n        return self.__slots__, (self.shape, self.requires_grad)\n\n    @classmethod\n    def __tensor_unflatten__(\n        cls, inner_tensors, flatten_spec, outer_size, outer_stride\n    ):\n        shape, requires_grad = flatten_spec\n        return cls(\n            shape,\n            **inner_tensors,\n            requires_grad=requires_grad,\n        )\n\n\nclass Sparse24TensorCutlass(Sparse24Tensor):\n    def _mm(\n        self,\n        B: torch.Tensor,\n        *,\n        bias: Optional[torch.Tensor] = None,\n        prefer_col_major_output: bool = False,\n    ) -> torch.Tensor:\n        if isinstance(B, Sparse24Tensor):\n            raise ValueError(\n                \"`Sparse24Tensor @ Sparse24Tensor` is not supported by the hardware\"\n            )\n        if bias is not None:\n            raise NotImplementedError(\n                f\"`Sparse24Tensor` with backend='{BACKEND_CUTLASS}' does not support matmul with bias. \"\n                f\"Remove the bias, or use backend='{BACKEND_CUSPARSELT}'\"\n            )\n        if self.ndim != 2 or B.ndim != 2:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: Broadcasting is not implemented\"\n            )\n        if self.shape[1] != B.shape[0]:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: invalid shapes \\\n    ({self.shape[0]}, {self.shape[1]}) @ ({B.shape[0]}, {B.shape[1]})\"\n            )\n        return Sp24Gemm.OPERATOR(self.packed, B, self.meta)[: self.shape[0]]\n\n    @classmethod\n    def __torch_dispatch__(cls, func, types, args=(), kwargs=None):\n        if func._overloadpacket not in SPARSE24_DISPATCH_CUTLASS:\n            raise NotImplementedError(\n                f\"{cls.__name__} only supports a specific set of operations, \"\n                f\"can't perform requested op ({func.__name__})\"\n            )\n        return SPARSE24_DISPATCH_CUTLASS[func._overloadpacket](\n            func, types, args, kwargs\n        )\n\n\n_CUSPLT_ALG_CACHE: Dict[Tuple[int, int, int, str, torch.dtype, bool], int] = {}\n# Disabled by default, as there is a correctness issue on cusparselt\n# when using some algorithms:\n# https://github.com/pytorch/pytorch/issues/155333\n_CUSPLT_TUNE = os.environ.get(\"XFORMERS_CUSPARSELT_TUNE\", \"0\") == \"1\"\n\n\ndef _cusplt_find_alg(\n    shape: List[int],\n    packed: torch.Tensor,\n    B: torch.Tensor,\n    bias: Optional[torch.Tensor],\n    transpose_result: bool,\n) -> int:\n    \"\"\"\n    cuSPARSELt has multiple algorithms (that correspond to different kernels)\n    to run a given GEMM, because the optimal kernel depends on the GEMM dimensions.\n    This function attempts to find the most efficient one by benchmarking all\n    of them.\n    NOTE: cuSPARSELt also provides a function to search the best algorithm\n    (exposed via `aten:_cslt_sparse_mm_search`) but it often fails to find the best\n    algorithm, so we need this workaround.\n    \"\"\"\n    if not _CUSPLT_TUNE:\n        return 0\n    M, K = shape\n    N = B.shape[1]\n    fmt = \"r\"\n    fmt += \"r\" if B.stride(-1) <= 1 else \"c\"\n    fmt += \"c\" if transpose_result else \"r\"\n    h = (M, N, K, fmt, B.dtype, bias is not None)\n    if h in _CUSPLT_ALG_CACHE:\n        return _CUSPLT_ALG_CACHE[h]\n\n    REPEAT = 10\n    TIME_ALGO = []\n    for algo in range(70):\n        has_error = False\n        for i in range(REPEAT):\n            try:\n                Sp24GemmCusplt.OPERATOR(\n                    packed, B, bias=bias, transpose_result=transpose_result, alg_id=algo\n                )\n            except RuntimeError:\n                has_error = True\n                break\n            if i == 1:  # 1 iteration of warmup\n                torch.cuda.synchronize()\n                t = time.monotonic()\n        if has_error:\n            break\n        torch.cuda.synchronize()\n        dt = time.monotonic() - t\n        TIME_ALGO.append((dt, algo))\n    TIME_ALGO.sort()\n    _CUSPLT_ALG_CACHE[h] = TIME_ALGO[0][1]\n    return _CUSPLT_ALG_CACHE[h]\n\n\n@torch.library.custom_op(\"xformers::_cusplt_mm\", mutates_args=(), device_types=[\"cuda\"])\ndef _cusplt_mm(\n    shape: List[int],\n    packed: torch.Tensor,\n    B: torch.Tensor,\n    bias: Optional[torch.Tensor],\n    transpose_result: bool,\n) -> torch.Tensor:\n    \"\"\"\n    This operator wraps find_algo + gemm. This is because we don't want find_algo\n    to be visible by torch compile, otherwise it will remove it from the graph.\n    \"\"\"\n    alg_id = _cusplt_find_alg(\n        shape, packed, B, bias=bias, transpose_result=transpose_result\n    )\n    return Sp24GemmCusplt.OPERATOR(\n        packed, B, bias=bias, transpose_result=transpose_result, alg_id=alg_id\n    )\n\n\n@torch.library.register_fake(\"xformers::_cusplt_mm\")\ndef _cusplt_mm_meta(\n    shape: List[int],\n    packed: torch.Tensor,\n    B: torch.Tensor,\n    bias: Optional[torch.Tensor],\n    transpose_result: bool,\n) -> torch.Tensor:\n    M, K = shape\n    N = B.shape[1]\n    if transpose_result:\n        return torch.empty([N, M], dtype=B.dtype, device=B.device)\n    return torch.empty([M, N], dtype=B.dtype, device=B.device)\n\n\nclass Sparse24TensorCuSparseLt(Sparse24Tensor):\n    def _mm(\n        self,\n        B: torch.Tensor,\n        *,\n        prefer_col_major_output: bool = False,\n        bias: Optional[torch.Tensor] = None,\n    ) -> torch.Tensor:\n        if isinstance(B, Sparse24Tensor):\n            raise ValueError(\n                \"`Sparse24Tensor @ Sparse24Tensor` is not supported by the hardware\"\n            )\n        if self.ndim != 2 or B.ndim != 2:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: Broadcasting is not implemented\"\n            )\n        if self.shape[1] != B.shape[0]:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: invalid shapes \\\n    ({self.shape[0]}, {self.shape[1]}) @ ({B.shape[0]}, {B.shape[1]})\"\n            )\n        if B.shape[1] % 8 != 0:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: trying to do `A={tuple(self.shape)} @ B={tuple(B.shape)}`. \"\n                \"The dense matrix B should have the second dimension aligned to 8.\"\n            )\n        if B.dtype != self.dtype:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: trying to do `A={tuple(self.shape)} @ B={tuple(B.shape)}`, \"\n                f\"with A.dtype={self.dtype} and B.dtype={B.dtype}. \"\n                \"This operation is only supported when A and B have the same data type.\"\n            )\n        if bias is not None and bias.dtype != self.dtype:\n            raise NotImplementedError(\n                f\"`{self.__class__.__name__}` matmul: trying to do `A={tuple(self.shape)} @ B={tuple(B.shape)} + C`, \"\n                f\"with A.dtype=B.dtype={self.dtype} and C.dtype={B.dtype}. \"\n                \"This operation is only supported when A, B and C have the same data type.\"\n            )\n        assert _has_cusparseLt()\n        out = torch.ops.xformers._cusplt_mm(\n            self.shape,\n            self.packed,\n            B,\n            bias=bias,\n            transpose_result=prefer_col_major_output,\n        )\n        if prefer_col_major_output:\n            out = out.t()\n        return out[: self.shape[0]]\n\n    @classmethod\n    def __torch_dispatch__(cls, func, types, args=(), kwargs=None):\n        if func._overloadpacket not in SPARSE24_DISPATCH_CUSPARSELT:\n            raise NotImplementedError(\n                f\"{cls.__name__} only supports a specific set of operations, \"\n                f\"can't perform requested op ({func.__name__})\"\n            )\n        return SPARSE24_DISPATCH_CUSPARSELT[func._overloadpacket](\n            func, types, args, kwargs\n        )\n\n\ntorch._dynamo.allow_in_graph(Sparse24TensorCuSparseLt)\ntorch._dynamo.allow_in_graph(Sparse24TensorCutlass)\n\nGRADIENT_SP24 = \"24sparse\"\nGRADIENT_DENSE = \"24dense\"\nGRADIENT_STE = \"ste\"  # Straight-Through Estimator\n\nBACKEND_CUTLASS = \"cutlass\"\nBACKEND_CUSPARSELT = \"cusparselt\"\nBACKEND_DENSE = \"dense\"\n\n\ndef _sparsify24_forward(x: torch.Tensor, *, algo: str, backend: str) -> Sparse24Tensor:\n    assert backend in [\n        BACKEND_CUTLASS,\n        BACKEND_CUSPARSELT,\n    ], f\"Invalid backend: {backend}\"\n    if isinstance(x, Sparse24Tensor):\n        if x.threads_masks is None:\n            raise ValueError(\"Input to `sparsify24` is already sparse\")\n        return x\n\n    packed, meta, packed_t, meta_t, threads_masks = SparsifyBothWays.OPERATOR(\n        x, algorithm=algo, backend=backend\n    )\n    cls = (\n        Sparse24TensorCutlass\n        if backend == BACKEND_CUTLASS\n        else Sparse24TensorCuSparseLt\n    )\n    return cls(\n        x.shape,\n        packed=packed,\n        meta=meta,\n        packed_t=packed_t,\n        meta_t=meta_t,\n        threads_masks=threads_masks,\n        requires_grad=False,\n    )\n\n\nclass _Sparsify24Func(torch.autograd.Function):\n    @staticmethod\n    def forward(ctx, x: torch.Tensor, algo: str, gradient: str, backend: str):  # type: ignore[override]\n        if gradient not in [GRADIENT_SP24, GRADIENT_DENSE, GRADIENT_STE]:\n            raise ValueError(\n                f\"Invalid gradient type: '{gradient}'. \"\n                f\"Expected '{GRADIENT_SP24}' or '{GRADIENT_DENSE}' or '{GRADIENT_STE}\"\n            )\n        out = _sparsify24_forward(x, algo=algo, backend=backend)\n        ctx.threads_masks = out.threads_masks\n        ctx.meta = out.meta\n        ctx.meta_t = out.meta_t\n        ctx.dtype = out.dtype\n        ctx.gradient = gradient\n        return out\n\n    @staticmethod\n    def backward(ctx, grad_out: torch.Tensor):  # type: ignore[override]\n        if isinstance(grad_out, Sparse24Tensor) or ctx.gradient == GRADIENT_STE:\n            return grad_out, None, None, None\n        assert not isinstance(grad_out, Sparse24Tensor)\n        assert grad_out.dtype == ctx.dtype\n        if ctx.gradient == GRADIENT_SP24:\n            packed, _, packed_t, _ = SparsifyApply.OPERATOR(grad_out, ctx.threads_masks)\n            grad_in: torch.Tensor = Sparse24TensorCutlass(\n                grad_out.shape,\n                packed,\n                ctx.meta,\n                packed_t,\n                ctx.meta_t,\n                ctx.threads_masks,\n                requires_grad=grad_out.requires_grad,\n            )\n        elif ctx.gradient == GRADIENT_DENSE:\n            assert ctx.threads_masks.is_contiguous()\n            grad_in = SparsifyApplyDenseOutput.OPERATOR(grad_out, ctx.threads_masks)\n        else:\n            assert False, f\"Unsupported gradient type: {ctx.gradient}\"\n        return (\n            grad_in,\n            None,\n            None,\n            None,\n        )\n\n\nclass _Sparsify24STEFunc(torch.autograd.Function):\n    @staticmethod\n    def forward(\n        ctx,\n        x: torch.Tensor,\n        algo: str,\n        backend: str,\n        bw_mul0: float,\n        bw_mul1: float,\n    ):  # type: ignore[override]\n        out = _sparsify24_forward(x, algo=algo, backend=backend)\n        ctx.threads_masks = out.threads_masks\n        ctx.bw_mul0 = bw_mul0\n        ctx.bw_mul1 = bw_mul1\n        return out\n\n    @staticmethod\n    def backward(ctx, grad_out: torch.Tensor):  # type: ignore[override]\n        assert not isinstance(grad_out, Sparse24Tensor)\n        if ctx.bw_mul0 == 1.0 and ctx.bw_mul1 == 1.0:\n            grad_in = grad_out\n        else:\n            grad_in = SparsifyApplyDenseOutput.OPERATOR(\n                grad_out, ctx.threads_masks, mul0=ctx.bw_mul0, mul1=ctx.bw_mul1\n            )\n        return (\n            grad_in,\n            None,  # algo\n            None,  # backend\n            None,  # bw_mul0\n            None,  # bw_mul1\n        )\n\n\nclass _Sparsify24LikeFunc(torch.autograd.Function):\n    @staticmethod\n    def forward(ctx, x: torch.Tensor, pattern: Sparse24Tensor, gradient: str, backend: str):  # type: ignore[override]\n        if not isinstance(pattern, Sparse24Tensor):\n            raise NotImplementedError(\n                \"`sparsify24_like`: `pattern` must be a sparse tensor\"\n            )\n        if not pattern.threads_masks.is_contiguous():\n            raise NotImplementedError(\n                \"`sparsify24_like` is not implemented when `pattern` is transposed\"\n            )\n        if gradient not in [GRADIENT_DENSE, GRADIENT_SP24, GRADIENT_STE]:\n            raise ValueError(f'`sparsify24_like`: invalid gradient type \"{gradient}\"')\n        ctx.threads_masks = pattern.threads_masks\n        ctx.meta = pattern.meta\n        ctx.meta_t = pattern.meta_t\n        ctx.dtype = pattern.dtype\n        ctx.gradient = gradient\n        if backend == BACKEND_DENSE:\n            assert ctx.threads_masks.is_contiguous()\n            return SparsifyApplyDenseOutput.OPERATOR(x, ctx.threads_masks)\n        packed, meta, packed_t, meta_t = SparsifyApply.OPERATOR(\n            x, ctx.threads_masks, backend=backend\n        )\n        if backend == BACKEND_CUTLASS:\n            return Sparse24TensorCutlass(\n                x.shape,\n                packed,\n                ctx.meta,\n                packed_t,\n                ctx.meta_t,\n                ctx.threads_masks,\n                requires_grad=x.requires_grad,\n            )\n        assert backend == BACKEND_CUSPARSELT, f\"Invalid backend: {backend}\"\n        meta.copy_(pattern.meta)\n        meta_t.copy_(pattern.meta_t)\n        return Sparse24TensorCuSparseLt(\n            x.shape,\n            packed,\n            meta,\n            packed_t,\n            meta_t,\n            ctx.threads_masks,\n            requires_grad=x.requires_grad,\n        )\n\n    @staticmethod\n    def backward(ctx, grad_out: torch.Tensor):  # type: ignore[override]\n        if ctx.gradient == GRADIENT_STE or isinstance(grad_out, Sparse24Tensor):\n            return grad_out, None, None, None\n        assert not isinstance(grad_out, Sparse24Tensor)\n        assert grad_out.dtype == ctx.dtype\n\n        if ctx.gradient == GRADIENT_DENSE:\n            assert ctx.threads_masks.is_contiguous()\n            return (\n                SparsifyApplyDenseOutput.OPERATOR(grad_out, ctx.threads_masks),\n                None,\n                None,\n                None,\n            )\n        assert ctx.gradient == GRADIENT_SP24\n\n        packed, _, packed_t, _ = SparsifyApply.OPERATOR(\n            grad_out, ctx.threads_masks, backend=BACKEND_CUTLASS\n        )\n        return (\n            Sparse24TensorCutlass(\n                grad_out.shape,\n                packed,\n                ctx.meta,\n                packed_t,\n                ctx.meta_t,\n                ctx.threads_masks,\n                requires_grad=grad_out.requires_grad,\n            ),\n            None,\n            None,\n            None,\n        )\n\n\n# We want to use `torch._dynamo.allow_in_graph` as a decorator\n# (see https://fburl.com/workplace/uimiz0mf) but it breaks mypy.\n# This is a hack to work around this\nF = TypeVar(\"F\", bound=Callable[..., Any])\n\n\ndef allow_in_graph(func: F) -> F:\n    return cast(F, torch._dynamo.allow_in_graph(func))\n\n\n@allow_in_graph\ndef sparsify24(\n    x: torch.Tensor,\n    algo: str = \"\",\n    gradient: str = GRADIENT_SP24,\n    backend: str = BACKEND_CUTLASS,\n) -> Sparse24Tensor:\n    return _Sparsify24Func.apply(x, algo, gradient, backend)\n\n\n@allow_in_graph\ndef sparsify24_ste(\n    x: torch.Tensor,\n    algo: str = \"\",\n    backend: str = BACKEND_CUTLASS,\n    bw_mul0: float = 1.0,\n    bw_mul1: float = 1.0,\n) -> Sparse24Tensor:\n    \"\"\"\n    2:4 sparsification, with Straight Through Estimator for the\n    backward pass (eg the gradient is *not* sparsified).\n    Optionally, `bw_mul[0-1]` provide the option to rescale the gradient\n    differently for pruned (`bw_mul0`) and kept values (`bw_mul1`).\n    \"\"\"\n    return _Sparsify24STEFunc.apply(x, algo, backend, bw_mul0, bw_mul1)\n\n\n@allow_in_graph\ndef sparsify24_like(\n    x: torch.Tensor,\n    pattern: torch.Tensor,\n    gradient: str = GRADIENT_SP24,\n    backend: str = \"\",\n    out_dense: Optional[bool] = None,  # <-- TODO: Deprecate this in favor of \"gradient\"\n) -> Sparse24Tensor:\n    if out_dense is not None and out_dense:\n        backend = BACKEND_DENSE\n    if backend == \"\":\n        backend = (\n            BACKEND_CUSPARSELT\n            if isinstance(pattern, Sparse24TensorCuSparseLt)\n            else BACKEND_CUTLASS\n        )\n    if not isinstance(pattern, Sparse24Tensor):\n        raise ValueError(\n            f\"`pattern` must be a `Sparse24Tensor` but got a {type(pattern)}\"\n        )\n    # Handle transposed case\n    if not pattern.threads_masks.is_contiguous():\n        return _Sparsify24LikeFunc.apply(x.t(), pattern.t(), gradient, backend).t()\n    return _Sparsify24LikeFunc.apply(x, pattern, gradient, backend)\n"
  },
  {
    "path": "xformers/ops/swiglu_op.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom dataclasses import dataclass\nfrom typing import Optional, Tuple, Union\n\nimport torch\nimport torch.nn.functional as F\nfrom torch import nn\n\nfrom .unbind import stack_or_none, unbind\n\n\nclass _SwiGLUDecomposedFunc(torch.autograd.Function):\n    \"\"\"\n    This is just an example implementation with all\n    operations explicited. This implementation is worse\n    than pytorch, because pytorch is able to fuse some operations\n    (eg the linear forward ...) that are decomposed here.\n\n    The time measurements were made on the ViT-Giant setting:\n    - A100/f16\n    - input: [4440, 1536]\n    - hidden: [4440, 4096]\n    \"\"\"\n\n    NAME = \"decomposed\"\n    FORCE_BW_F32 = False\n\n    def _silu_backward(dy, x):\n        # https://github.com/pytorch/pytorch/blob/563b065f5a4b4055fa6b025c2514b566d5fd9439/aten/src/ATen/native/Activation.cpp#L483\n        sigm = 1 / (1 + torch.exp(-x.float()))\n        return (dy.float() * sigm * (1 + x.float() * (1 - sigm))).to(x.dtype)\n\n    # 952us\n    @classmethod\n    def forward(cls, ctx, x, w1, b1, w2, b2, w3, b3):\n        x1 = x @ w1.transpose(-2, -1) + b1  # 275us\n        x2 = x @ w2.transpose(-2, -1) + b2  # 275us\n        x3 = F.silu(x1)  # 62us\n        x4 = x3 * x2  # 90us\n        x5 = x4 @ w3.transpose(-2, -1) + b3  # 250us\n\n        ctx.save_for_backward(x, w1, b1, w2, b2, w3, b3, x1, x2, x3, x4, x5)\n        return x5\n\n    # 1900us\n    @classmethod\n    def backward(cls, ctx, dx5):\n        saved_tensors = ctx.saved_tensors\n        if cls.FORCE_BW_F32:\n            dx5 = dx5.float()\n            saved_tensors = [t.float() for t in ctx.saved_tensors]\n        x, w1, b1, w2, b2, w3, b3, x1, x2, x3, x4, x5 = saved_tensors\n        dx4 = dx5 @ w3  # 255us (nn)\n        dw3 = dx5.transpose(-2, -1) @ x4  # 247us (nt)\n        db3 = dx5.sum(0)  # 25us\n        dx3 = dx4 * x2  # 88us\n        dx2 = dx4 * x3  # 88us\n        dx1 = cls._silu_backward(dx3, x1)  # 90us\n        dx = dx2 @ w2  # 260us (nn)\n        dw2 = dx2.transpose(-2, -1) @ x  # 245us (nt)\n        db2 = dx2.sum(0)  # 50us\n        dx += dx1 @ w1  # 260us (nn)\n        dw1 = dx1.transpose(-2, -1) @ x  # 245us (nt)\n        db1 = dx1.sum(0)  # 50us\n        return (dx, dw1, db1, dw2, db2, dw3, db3)\n\n\nclass SwiGLUOp:\n    \"\"\"Base class for any swiglu operator in :attr:`xformers.ops.swiglu`\"\"\"\n\n    def __init__(self, op, packed_weights: bool, name: str, constraints):\n        self.NAME = name\n        self.PACKED_WEIGHTS = packed_weights\n        self.op = op\n        self.constraints = constraints\n\n    def supports(self, op: \"SwiGLUOpDispatch\") -> bool:\n        if self.PACKED_WEIGHTS and not op.packed_weights:\n            return False\n        return all(c(op) for c in self.constraints)\n\n    def __call__(self, *args: Optional[torch.Tensor]) -> torch.Tensor:\n        raise NotImplementedError()\n\n    def __str__(self) -> str:\n        return f\"SwiGLUOp:{self.NAME}\"\n\n\nclass _ForwardToPythonAutogradFunc(SwiGLUOp):\n    def supports(self, op: \"SwiGLUOpDispatch\") -> bool:\n        return super().supports(op)\n\n    def __call__(self, *args, **kwargs):\n        return self.op.apply(*args, **kwargs)\n\n\nclass _ForwardToFunc(SwiGLUOp):\n    def __call__(self, *args, **kwargs):\n        return self.op(*args, **kwargs)\n\n    def info(self):\n        if self.op.__name__ == \"no_such_operator\":\n            return \"not built\"\n        return \"available\"\n\n\ndef _eager_functional_swiglu(\n    x: torch.Tensor,\n    w1: torch.Tensor,\n    b1: Optional[torch.Tensor],\n    w2: torch.Tensor,\n    b2: Optional[torch.Tensor],\n    w3: torch.Tensor,\n    b3: Optional[torch.Tensor],\n) -> torch.Tensor:\n    x1 = F.linear(x, w1, b1)\n    x2 = F.linear(x, w2, b2)\n    hidden = F.silu(x1) * x2\n    return F.linear(hidden, w3, b3)\n\n\n@dataclass\nclass SwiGLUOpDispatch:\n    \"\"\"Dispatcher to automatically select\n    the best operator in :attr:`xformers.ops.swiglu`\n    \"\"\"\n\n    device: Union[torch.device, str]\n    dtype: torch.dtype\n    dtype_autocast_gpu: Optional[torch.dtype]\n    packed_weights: bool\n    bias_enabled: bool\n\n    @property\n    def op(self) -> SwiGLUOp:\n        \"\"\"Computes the best operator\n\n        Returns:\n            SwiGLUOp: The best operator for the configuration\n        \"\"\"\n        return SwiGLUEagerOp\n\n    @staticmethod\n    def from_arguments(\n        x: torch.Tensor,\n        w1: torch.Tensor,\n        b1: Optional[torch.Tensor],\n        w2: torch.Tensor,\n        b2: Optional[torch.Tensor],\n        w3: torch.Tensor,\n        b3: Optional[torch.Tensor],\n    ) -> \"SwiGLUOpDispatch\":\n        return SwiGLUOpDispatch(\n            device=x.device,\n            dtype=x.dtype,\n            packed_weights=stack_or_none((w1, w2), dim=0) is not None,\n            dtype_autocast_gpu=(\n                torch.get_autocast_gpu_dtype()\n                if torch.is_autocast_enabled()\n                else w1.dtype\n            ),\n            bias_enabled=b1 is not None and b2 is not None and b3 is not None,\n        )\n\n\ndef _bias_enabled(op: SwiGLUOpDispatch) -> bool:\n    return op.bias_enabled\n\n\n_SwiGLUDecomposedOp = _ForwardToPythonAutogradFunc(\n    _SwiGLUDecomposedFunc, False, \"decomposed\", constraints=[_bias_enabled]\n)\nSwiGLUEagerOp = _ForwardToFunc(\n    _eager_functional_swiglu,\n    False,\n    \"eager\",\n    constraints=[],\n)\n\n\ndef swiglu(\n    x: torch.Tensor,\n    w1: torch.Tensor,\n    b1: Optional[torch.Tensor],\n    w2: torch.Tensor,\n    b2: Optional[torch.Tensor],\n    w3: torch.Tensor,\n    b3: Optional[torch.Tensor],\n    *,\n    op: Optional[SwiGLUOp] = None,\n) -> torch.Tensor:\n    \"\"\"\n    Computes a SwiGLU block given the weights/bias of the 3\n    linear layers.\n\n    - It is recommended to keep ``op=None`` so the best implementation \\\n    available for the inputs will be used.\n\n\n    :Equivalent pytorch code:\n\n    .. code-block:: python\n\n        x1 = F.linear(x, w1, b1)\n        x2 = F.linear(x, w2, b2)\n        hidden = F.silu(x1) * x2\n        return F.linear(hidden, w3, b3)\n\n    :Packing weights:\n\n    To allow faster implementations, it's recommended to have w1/w2 come from the same storage, as in:\n        .. code-block:: python\n\n            w1, w2 = xformers.ops.unbind(w12, 0)\n\n    :Supported hardware:\n\n    This operator is only optimized on A100+ on ``torch.half`` or ``torch.bfloat16`` \\\n        (autocast is supported), and will fallback to a functional pytorch \\\n        implementation otherwise.\n    \"\"\"\n\n    batch_shape = x.shape[:-1]\n    x = x.reshape([-1, x.shape[-1]])\n    if w1.ndim != 2 or w1.shape != w2.shape:\n        raise ValueError(f\"Invalid shapes for w1: {w1.shape} / w2: {w2.shape}\")\n    if b1 is not None:\n        if b1.ndim != 1 or b1.shape[0] != w1.shape[0]:\n            raise ValueError(f\"Invalid shapes for b1: {b1.shape}\")\n    if b2 is not None:\n        if b2.ndim != 1 or b2.shape[0] != w2.shape[0]:\n            raise ValueError(f\"Invalid shapes for b2: {b2.shape}\")\n    if w3.ndim != 2 or w3.shape[1] != w2.shape[0]:\n        raise ValueError(f\"Invalid shape for w3: {w3.shape}\")\n    if b3 is not None:\n        if b3.ndim != 1 or b3.shape[0] != w3.shape[0]:\n            raise ValueError(f\"Invalid shapes for w3: {w3.shape} / b3: {b3.shape}\")\n\n    if op is None:\n        op = SwiGLUOpDispatch.from_arguments(x, w1, b1, w2, b2, w3, b3).op\n\n    if not op.PACKED_WEIGHTS:\n        return op(x, w1, b1, w2, b2, w3, b3).reshape([*batch_shape, -1])\n    w1w2 = stack_or_none((w1, w2), dim=0)\n    if b1 is not None and b2 is not None:\n        b1b2: Optional[torch.Tensor] = stack_or_none((b1, b2), dim=0)\n        if b1b2 is None:\n            raise NotImplementedError(\"b1/b2 needs to be properly packed\")\n    else:\n        b1b2 = None\n        assert b1 is None and b2 is None\n\n    if w1w2 is None:\n        raise NotImplementedError(\"w1/w2 needs to be properly packed\")\n    return op(x, w1w2, b1b2, w3, b3).reshape([*batch_shape, -1])\n\n\ndef swiglu_packed(\n    x: torch.Tensor,\n    w1w2: torch.Tensor,\n    b1b2: Optional[torch.Tensor],\n    w3: torch.Tensor,\n    b3: Optional[torch.Tensor],\n    *,\n    op: SwiGLUOp,\n) -> torch.Tensor:\n    \"\"\"\n    Computes a SwiGLU block given the weights/bias of the 3\n    linear layers.\n\n    :Equivalent pytorch code:\n\n    .. code-block:: python\n\n        x1 = F.linear(x, w1, b1)\n        x2 = F.linear(x, w2, b2)\n        hidden = F.silu(x1) * x2\n        return F.linear(hidden, w3, b3)\n\n    :Supported hardware:\n\n    This operator is only optimized on A100+ on ``torch.half`` or ``torch.bfloat16`` \\\n        (autocast is supported), and will fallback to a functional pytorch \\\n        implementation otherwise.\n    \"\"\"\n    batch_shape = x.shape[:-1]\n    x = x.reshape([-1, x.shape[-1]])\n\n    if b3 is not None:\n        if b3.ndim != 1 or b3.shape[0] != w3.shape[0]:\n            raise ValueError(f\"Invalid shapes for w3: {w3.shape} / b3: {b3.shape}\")\n\n    assert op.PACKED_WEIGHTS, \"Not implemented PACKED_WEIGHTS\"\n\n    return op(x, w1w2, b1b2, w3, b3).reshape([*batch_shape, -1])\n\n\nclass SwiGLU(nn.Module):\n    \"\"\"\n    A Module that encapsulates the call to :attr:`xformers.ops.swiglu`,\n    and holds the weights for the 3 linear layers\n    \"\"\"\n\n    def __init__(\n        self,\n        in_features: int,\n        hidden_features: int,\n        out_features: Optional[int] = None,\n        bias: bool = True,\n        *,\n        _pack_weights: bool = True,\n    ) -> None:\n        \"\"\"Create a SwiGLU module\n\n        Args:\n            in_features (int): Number of features of the input\n            hidden_features (int): Number of hidden features\n            out_features (Optional[int], optional): Number of features of the input. Defaults to None.\n            bias (bool, optional): Whether linear layers also include a bias. Defaults to True.\n        \"\"\"\n        super().__init__()\n        out_features = out_features or in_features\n        hidden_features = hidden_features or in_features\n\n        self.w12: Optional[nn.Linear]\n        if _pack_weights:\n            self.w12 = nn.Linear(in_features, 2 * hidden_features, bias=bias)\n        else:\n            self.w12 = None\n            self.w1 = nn.Linear(in_features, hidden_features, bias=bias)\n            self.w2 = nn.Linear(in_features, hidden_features, bias=bias)\n        self.w3 = nn.Linear(hidden_features, out_features, bias=bias)\n\n        self.hidden_features = hidden_features\n        self.out_features = out_features\n        self.in_features = in_features\n        self.op: Optional[SwiGLUOp] = None\n\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \"\"\"Computes :attr:`swiglu` with the module's weights\n\n        Args:\n            x (torch.Tensor): A Tensor of shape ``[..., in_features]``\n\n        Returns:\n            torch.Tensor: A Tensor of shape ``[..., out_features]``\n        \"\"\"\n        if self.w12 is not None:\n            if self.op is not None:\n                assert (\n                    self.op.PACKED_WEIGHTS\n                ), \"_pack_weights and self.op.PACKED_WEIGHTS should match\"\n                return swiglu_packed(x, *self._packed_ordered_params(), op=self.op)\n\n        return swiglu(x, *self._ordered_params(), op=self.op)\n\n    def _ordered_params(\n        self,\n    ) -> Tuple[\n        torch.Tensor,\n        Optional[torch.Tensor],\n        torch.Tensor,\n        Optional[torch.Tensor],\n        torch.Tensor,\n        Optional[torch.Tensor],\n    ]:\n        \"\"\"Used for testing - returns ordered arguments for operators\"\"\"\n        b1: Optional[torch.Tensor]\n        b2: Optional[torch.Tensor]\n        if self.w12 is not None:\n            w1w2 = self.w12.weight\n            b1b2 = self.w12.bias\n            w1, w2 = unbind(\n                w1w2.view([2, w1w2.shape[0] // 2, w1w2.shape[1]]),\n                dim=0,\n            )\n            if b1b2 is not None:\n                b1, b2 = unbind(b1b2.view([2, b1b2.shape[0] // 2]), dim=0)\n            else:\n                b1, b2 = None, None\n        else:\n            w1, w2 = self.w1.weight, self.w2.weight\n            b1, b2 = self.w1.bias, self.w2.bias\n\n        return (\n            w1,\n            b1,\n            w2,\n            b2,\n            self.w3.weight,\n            self.w3.bias,\n        )\n\n    def _packed_ordered_params(\n        self,\n    ) -> Tuple[\n        torch.Tensor,\n        Optional[torch.Tensor],\n        torch.Tensor,\n        Optional[torch.Tensor],\n    ]:\n        assert self.w12 is not None, \"Packed weights are only available when using w12\"\n\n        \"\"\"Used for testing - returns ordered arguments for packed operators\"\"\"\n        w1w2 = self.w12.weight\n        b1b2_param = self.w12.bias\n\n        w1w2 = w1w2.view([2, w1w2.shape[0] // 2, w1w2.shape[1]])\n\n        b1b2: Optional[torch.Tensor] = None\n        if b1b2_param is not None:\n            b1b2 = b1b2_param.view([2, b1b2_param.shape[0] // 2])\n\n        return (\n            w1w2,\n            b1b2,\n            self.w3.weight,\n            self.w3.bias,\n        )\n"
  },
  {
    "path": "xformers/ops/tiled_matmul.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport os\nfrom typing import List, Tuple\n\nimport torch\n\nfrom .. import _is_triton_available\n\n\n# Copied over from the sequence parallel fused ops.\ndef _should_use_triton(device: torch.device, dtype: torch.dtype) -> bool:\n    if not int(os.getenv(\"XFORMERS_TILED_MATMUL_ENABLE_TRITON\", \"1\")):\n        return False\n    if not _is_triton_available():\n        return False\n    device_capability = torch.cuda.get_device_capability(device)\n    # Triton seems to be having issues on P100 and V100 GPUs, such as\n    # https://github.com/openai/triton/issues/1609\n    # https://github.com/openai/triton/issues/1610\n    # https://github.com/openai/triton/issues/1257#issuecomment-1532616965\n    # and, in recent Triton versions (Jan 2024), returning wrong values.\n    if device_capability < (8, 0):\n        return False\n    return True\n\n\ndef check_inputs(\n    a: List[List[torch.Tensor]],\n    b: List[List[torch.Tensor]],\n) -> Tuple[List[int], List[int], List[int]]:\n    assert len(a) >= 1 and len(a[0]) >= 1 and all(len(row) == len(a[0]) for row in a), (\n        \"the first operand must be a non-empty two-dimensional regular list of lists \"\n        \"of tenors\"\n    )\n    assert len(b) >= 1 and len(b[0]) >= 1 and all(len(row) == len(b[0]) for row in b), (\n        \"the second operand must be a non-empty two-dimensional regular list of lists \"\n        \"of tenors\"\n    )\n\n    m_tiles = len(a)\n    k_tiles = len(a[0])\n    assert len(b) == k_tiles, (\n        \"the first operand's inner dimension must match the second operand's outer \"\n        f\"dimension, got {k_tiles} and {len(b)}\"\n    )\n    n_tiles = len(b[0])\n\n    ms = [a[tile_m][0].shape[0] for tile_m in range(m_tiles)]\n    ns = [b[0][tile_n].shape[1] for tile_n in range(n_tiles)]\n    aks = [a[0][tile_k].shape[1] for tile_k in range(k_tiles)]\n    bks = [b[tile_k][0].shape[0] for tile_k in range(k_tiles)]\n\n    for tile_m in range(m_tiles):\n        for tile_k in range(k_tiles):\n            assert a[tile_m][tile_k].shape[0] == ms[tile_m], (\n                f\"the tensors on row {tile_m} of the first operand must all have the \"\n                f\"same size along the m dimension, got {ms[tile_m]} at position 0 and \"\n                f\"{a[tile_m][tile_k].shape[0]} at position {tile_k}\"\n            )\n            assert a[tile_m][tile_k].shape[1] == aks[tile_k], (\n                f\"the tensors on column {tile_k} of the first operand must all have \"\n                f\"the same size along the k dimension, got {aks[tile_k]} at position 0 \"\n                f\"and {a[tile_m][tile_k].shape[1]} at position {tile_m}\"\n            )\n\n    for tile_n in range(n_tiles):\n        for tile_k in range(k_tiles):\n            assert b[tile_k][tile_n].shape[0] == bks[tile_k], (\n                f\"the tensors on row {tile_k} of the second operand must all have the \"\n                f\"same size along the k dimension, got {bks[tile_k]} at position 0 and \"\n                f\"{b[tile_k][tile_n].shape[0]} at position {tile_n}\"\n            )\n            assert b[tile_k][tile_n].shape[1] == ns[tile_n], (\n                f\"the tensors on column {tile_n} of the second operand must all have \"\n                f\"the same size along the n dimension, got {ns[tile_n]} at position 0 \"\n                f\"and {b[tile_k][tile_n].shape[1]} at position {tile_k}\"\n            )\n\n    for tile_k in range(k_tiles):\n        assert aks[tile_k] == bks[tile_k], (\n            f\"the tensors on column {tile_k} of the first operand and those on row \"\n            f\"{tile_k} of the second operand must have the same size along the k \"\n            f\"dimension, got {aks[tile_k]} and {bks[tile_k]}\"\n        )\n    ks = aks\n\n    return ms, ns, ks\n\n\ndef check_output(out: List[List[torch.Tensor]], ms: List[int], ns: List[int]) -> None:\n    m_tiles, n_tiles = len(ms), len(ns)\n    assert (\n        len(out) >= 1\n        and len(out[0]) >= 1\n        and all(len(row) == len(out[0]) for row in out)\n    ), \"out must be a non-empty two-dimensional regular list of lists of tenors\"\n    assert len(out) == m_tiles\n    assert len(out[0]) == n_tiles\n    cms = [out[tile_m][0].shape[0] for tile_m in range(m_tiles)]\n    cns = [out[0][tile_n].shape[1] for tile_n in range(n_tiles)]\n    for tile_m in range(m_tiles):\n        for tile_n in range(n_tiles):\n            assert out[tile_m][tile_n].shape[0] == cms[tile_m], (\n                f\"the tensors on row {tile_m} of out must all have the same size \"\n                f\"along the m dimension, got {cms[tile_m]} at position 0 and \"\n                f\"{out[tile_m][tile_n].shape[0]} at position {tile_n}\"\n            )\n            assert out[tile_m][tile_n].shape[1] == cns[tile_n], (\n                f\"the tensors on column {tile_n} of out must all have the same \"\n                f\"size along the k dimension, got {cns[tile_n]} at position 0 and \"\n                f\"{out[tile_m][tile_n].shape[1]} at position {tile_m}\"\n            )\n    for tile_m in range(m_tiles):\n        assert cms[tile_m] == ms[tile_m], (\n            f\"the tensors on row {tile_m} of out and those on row {tile_m} of the \"\n            f\"first operand must have the same size along the m dimension, got \"\n            f\"{cms[tile_m]} and {ms[tile_m]}\"\n        )\n    for tile_n in range(n_tiles):\n        assert cns[tile_n] == ns[tile_n], (\n            f\"the tensors on column {tile_n} of out and those on column {tile_n} \"\n            f\"of the second operand must have the same size along the n dimension, \"\n            f\"got {cns[tile_n]} and {ns[tile_n]}\"\n        )\n\n\n# Using out= args in PyTorch is complicated, especially with custom_ops and\n# torch.compile (we need to declare our side-effects with mutates_args, which\n# then requires a functionalization step, ...). Thus this out= variant of the\n# operator is exposed as a PLAIN PYTHON function, and is not compilable nor\n# differentiable. It needs to be invoked from within a custom_op elsewhere.\ndef tiled_matmul_out(\n    a: List[List[torch.Tensor]],\n    b: List[List[torch.Tensor]],\n    out: List[List[torch.Tensor]],\n) -> None:\n    ms, ns, ks = check_inputs(a, b)\n    check_output(out, ms, ns)\n\n    # TODO We can try merging tiles that come from contiguous memory, using\n    # stack_or_none, to further improve performance.\n\n    # Because the Triton kernel is hardcoded for maximum three tiles.\n    # Because, in turn, we aimed this at the fusion of wq/wk/wv.\n    if (\n        len(ms) <= 3\n        and len(ks) <= 3\n        and len(ns) <= 3\n        and _should_use_triton(a[0][0].device, a[0][0].dtype)\n    ):\n        from ._triton.tiled_matmul_kernels import _launch_triton_matmul\n\n        _launch_triton_matmul(a, b, out, ms, ns, ks)\n    else:\n        for tile_m in range(len(ms)):\n            for tile_n in range(len(ns)):\n                torch.mm(a[tile_m][0], b[0][tile_n], out=out[tile_m][tile_n])\n                for tile_k in range(1, len(ks)):\n                    out[tile_m][tile_n].addmm_(a[tile_m][tile_k], b[tile_k][tile_n])\n\n\ndef _flatten(x: List[List[torch.Tensor]], rows: int, cols: int) -> List[torch.Tensor]:\n    assert len(x) == rows\n    assert all(len(row) == cols for row in x)\n    flat_x = [elem for row in x for elem in row]\n    assert len(flat_x) == rows * cols\n    return flat_x\n\n\ndef _unflatten(\n    flat_x: List[torch.Tensor], rows: int, cols: int\n) -> List[List[torch.Tensor]]:\n    assert len(flat_x) == cols * rows\n    x = [\n        flat_x[row_offset : row_offset + cols]\n        for row_offset in range(0, rows * cols, cols)\n    ]\n    assert len(x) == rows\n    assert all(len(row) == cols for row in x)\n    return x\n\n\ndef _flattened_transpose(\n    flat_x: List[torch.Tensor], rows: int, cols: int\n) -> List[torch.Tensor]:\n    x = _unflatten(flat_x, rows, cols)\n    transposed_x = [[elem.t() for elem in col] for col in zip(*x)]\n    flat_transposed_x = _flatten(transposed_x, cols, rows)\n    return flat_transposed_x\n\n\n# PyTorch (custom_op and torch.compile, but also the dispatcher in general)\n# have a hard time with Tensor[][] args. Thus we flatten them into Tensor[] to\n# pass them into and out of this operator.\n# See: https://github.com/pytorch/pytorch/issues/113022\n@torch.library.custom_op(\n    \"xformers_python::tiled_matmul_fwd\",\n    mutates_args=(),\n    device_types=\"cuda\",\n)\ndef tiled_matmul_fwd(\n    flat_a: List[torch.Tensor],\n    flat_b: List[torch.Tensor],\n    ms: List[int],\n    ns: List[int],\n    ks: List[int],\n) -> List[torch.Tensor]:\n    a = _unflatten(flat_a, len(ms), len(ks))\n    b = _unflatten(flat_b, len(ks), len(ns))\n\n    c = [[a[0][0].new_empty((m, n)) for n in ns] for m in ms]\n    tiled_matmul_out(a, b, out=c)\n\n    return _flatten(c, len(ms), len(ns))\n\n\n@torch.library.register_fake(\"xformers_python::tiled_matmul_fwd\")\ndef tiled_matmul_fwd_fake(\n    flat_a: List[torch.Tensor],\n    flat_b: List[torch.Tensor],\n    ms: List[int],\n    ns: List[int],\n    ks: List[int],\n) -> List[torch.Tensor]:\n    c = [[flat_a[0][0].new_empty((m, n)) for n in ns] for m in ms]\n    return _flatten(c, len(ms), len(ks))\n\n\ndef tiled_matmul_setup_context(ctx, inputs, output):\n    flat_a, flat_b, ctx.ms, ctx.ns, ctx.ks = inputs\n    ctx.save_for_backward(*flat_a, *flat_b)\n\n\ndef tiled_matmul_bwd(ctx, flat_grad_c):\n    assert len(ctx.saved_tensors) == len(ctx.ms) * len(ctx.ks) + len(ctx.ks) * len(\n        ctx.ns\n    )\n    flat_a = ctx.saved_tensors[: len(ctx.ms) * len(ctx.ks)]\n    flat_b = ctx.saved_tensors[-len(ctx.ks) * len(ctx.ns) :]\n\n    flat_transposed_a = _flattened_transpose(flat_a, len(ctx.ms), len(ctx.ks))\n    flat_transposed_b = _flattened_transpose(flat_b, len(ctx.ks), len(ctx.ns))\n\n    flat_grad_a = tiled_matmul_fwd(\n        flat_grad_c, flat_transposed_b, ctx.ms, ctx.ks, ctx.ns\n    )\n    flat_grad_b = tiled_matmul_fwd(\n        flat_transposed_a, flat_grad_c, ctx.ks, ctx.ns, ctx.ms\n    )\n\n    return flat_grad_a, flat_grad_b, None, None, None\n\n\ntorch.library.register_autograd(\n    \"xformers_python::tiled_matmul_fwd\",\n    tiled_matmul_bwd,\n    setup_context=tiled_matmul_setup_context,\n)\n\n\ndef tiled_matmul(\n    a: List[List[torch.Tensor]],\n    b: List[List[torch.Tensor]],\n) -> List[List[torch.Tensor]]:\n    \"\"\"Multiply two matrices given as grids of tiles\n\n    It performs the matmul between A and B, which are given as two-dimensional\n    grids of tiles (i.e., blocks), represented as lists of lists of tensors.\n    The output will itself be a matrix in such a form. Formally:\n\n        out[m][n] = sum(a[m][k] @ b[k][n] for k in range(...))\n\n    with the obvious constraints needed to make it work, in terms of number of\n    tiles and sizes of each tile.\n\n    The interest of this operator is to improve performance by avoding wave\n    quantization effects when doing independent matrix multiplications in\n    series. Sometimes, when these matmuls have one operand in common, this can\n    also be addressed by concatenating the other operands into a single matrix,\n    and issuing a single matmul. However this isn't always possible (e.g., might\n    break the checkpoint format) and it's an anti-pattern, as it obscures the\n    logic (e.g., changing the modelling code out of performance reasons). This\n    tiled matmul performs the same computation as if the matrices were merged,\n    without merging them, simply through a smarter memory addressing scheme.\n\n    The tiled matmul is less generic than a grouped matmul, which can also help\n    with wave quantization, and doesn't need the matmuls to have the same lhs\n    or rhs operand. However, a grouped matmul will write the result of each\n    matmul to a separate output matrix, whereas the tiled matmul allows to add\n    them together into a single output. This is needed during the backward pass\n    of a linear layer, and it's the reason we wrote this instead of using a\n    grouped matmul.\n\n    The tiled matmul is implemented using a custom Triton kernel, which puts\n    constraints on the strides of the tiles. All rows of A must have the same\n    K stride, all columns of A must have the same M stride, and so on.\n\n    Currently the tiled matmul supports at most three tiles on each dimension,\n    although fewer can also be given. This is because we needed it to fuse the\n    query, key and value weights of an attention layer. This limit can be\n    increased if needed.\n\n    This operator is differentiable.\n\n    \"\"\"\n    # Inputs are checked inside the op, but we check them as well to make sure\n    # that they are \"regular\" and can be flattened.\n    ms, ns, ks = check_inputs(a, b)\n    flat_a = _flatten(a, len(ms), len(ks))\n    flat_b = _flatten(b, len(ks), len(ns))\n    flat_c = tiled_matmul_fwd(flat_a, flat_b, ms, ns, ks)\n    c = _unflatten(flat_c, len(ms), len(ns))\n    return c\n"
  },
  {
    "path": "xformers/ops/tree_attention.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport itertools\nfrom dataclasses import dataclass\nfrom functools import lru_cache\nfrom typing import List, Optional, Sequence, Tuple, Type\n\nimport torch\n\nfrom xformers.ops import fmha\n\nfrom .fmha import (\n    flash,\n    flash3,\n    memory_efficient_attention_forward_requires_grad,\n    memory_efficient_attention_partial,\n    merge_attentions,\n    triton_splitk,\n)\nfrom .fmha.attn_bias import (\n    AttentionBias,\n    PagedBlockDiagonalGappyKeysMask,\n    PagedBlockDiagonalPaddedKeysMask,\n)\nfrom .fmha.common import AttentionFwOpBase\nfrom .fmha.dispatch import _get_use_fa3, fa3_available\n\n\n@dataclass\nclass TreeAttnMetadata:\n    \"\"\"\n    tree_choices: definition of the tree, tuples sorted by length, each corresponding\n        to a node. See the docstring of TreeAttnMetadata.from_tree_choices.\n    attention_bias: Medusa-style tree attention bias as an explicit tensor\n        of shape (tree_size, tree_size), where tree_size is the total number\n        of nodes in the tree. It can be used as a spec_attn_bias (\"right\"\n        or \"suffix\" attention part) in tree_attention.\n        See tree_attention_with_sync for a usage example.\n    tree_indices: 1D tensor of size tree_size which maps tree nodes to draft tokens.\n        Tree nodes are assumed to be in the same order as in tree_choices\n        (see TreeAttnMetadata.from_tree_choices).\n    retrieval_indices: a tensor of shape (number of leaves, depth + 1), where one\n        row corresponds to one path, and contains indices of the tree nodes\n        on that path. Paths are padded with -1 from the right.\n        The paths (row dim) are unsorted.\n    path_lengths: real lengths for each of the paths.\n    tree_seq_position_ids: 1D tensor of size tree_size which indicates which head\n        a node belongs to. Equivalently, it shows the sequence position of the\n        node within the corresponding path.\n    parent_node_indices: 1D tensor of size tree_size which for each node contains\n        position of its parent + 1. For root node(s) it contains 0.\n    child_node_indices: a tensor of shape (tree_size, max_num_children_per_node),\n        in which each row contains indices of children of the corresponding node.\n        Rows corresponding to nodes which have less than max_num_children_per_node\n        children are padded by repeating the last child index.\n        For leaf nodes the values are meaningless and filled with 0.\n    num_children_per_node: 1D tensor of size tree_size which contains the number of\n        children for each node.\n    candidate_idx: 1D tensor of size tree_size, contains index of each node among its \"siblings\".\n        Takes values from 0 to the number of children of the parent node minus 1.\n    num_nodes_per_level: 1D tensor of the number of nodes at each level (including root).\n    num_children_per_node_at_level: List of 1D tensors, each containing the number of children at the tree level.\n    subtree_size: List of integers, each containing the number of nodes in the subtree at the tree level.\n    Example:\n        Tree choices\n          `[(0,), (0, 0), (0, 1), (0, 2), (1,), (1, 0), (1, 1), (1, 2), (2,), (2, 0), (2, 1), (2, 2)]`\n        represents a tree that looks like this:\n            0\n            |-- 1\n            |   |-- 4\n            |   |-- 5\n            |   |-- 6\n            |\n            |-- 2\n            |   |-- 7\n            |   |-- 8\n            |   |-- 9\n            |\n            |-- 3\n                |-- 10\n                |-- 11\n                |-- 12\n\n        with TreeAttnMetadata\n            tree_indices=tensor([0, 1, 2, 3, 4, 5, 6, 4, 5, 6, 4, 5, 6])\n            retrieval_indices=tensor([[ 0,  1,  5],\n                                      [ 0,  2,  9],\n                                      [ 0,  3, 11],\n                                      [ 0,  1,  4],\n                                      [ 0,  2,  8],\n                                      [ 0,  3, 10],\n                                      [ 0,  1,  6],\n                                      [ 0,  2,  7],\n                                      [ 0,  3, 12]])\n            path_lengths=[3, 3, 3, 3, 3, 3, 3, 3, 3]\n            tree_seq_position_ids=tensor([0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2])\n            child_node_indices=tensor([[ 0,  1,  2],\n                                       [ 3,  4,  5],\n                                       [ 6,  7,  8],\n                                       [ 9, 10, 11],\n                                       [ 0,  0,  0],\n                                       ...\n                                       [ 0,  0,  0]])\n            num_children_per_node=tensor([3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0])\n            candidate_idx=tensor([0, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2])\n            num_nodes_per_level=tensor([1, 3, 3])\n            num_children_per_node_at_level=[tensor([3]), tensor([3, 3, 3]), tensor([0, 0, 0, 0, 0, 0, 0, 0, 0])]\n            subtree_sizes=[1, 4, 13]\n    \"\"\"\n\n    tree_choices: Sequence[Tuple[int, ...]]\n    attention_bias: torch.Tensor\n    tree_indices: torch.Tensor\n    retrieval_indices: torch.Tensor\n    path_lengths: List[int]\n    tree_seq_position_ids: torch.Tensor\n    parent_node_indices: torch.Tensor\n    child_node_indices: torch.Tensor\n    num_children_per_node: torch.Tensor\n    candidate_idx: torch.Tensor\n    num_nodes_per_level: torch.Tensor\n    num_nodes_per_level_cpu: torch.Tensor\n    num_children_per_node_at_level: List[torch.Tensor]\n    num_children_per_node_at_level_cpu: List[torch.Tensor]\n    subtree_sizes: List[int]\n\n    @classmethod\n    @lru_cache\n    def from_tree_choices_cached(\n        cls,\n        tree_choices: Tuple[Tuple[int, ...]],\n        dtype: Optional[torch.dtype] = None,\n        device: Optional[torch.device] = None,\n    ) -> \"TreeAttnMetadata\":\n        return cls.from_tree_choices(tree_choices, dtype, device)\n\n    @classmethod\n    def from_tree_choices(\n        cls,\n        tree_choices: Sequence[Tuple[int, ...]],\n        dtype: Optional[torch.dtype] = None,\n        device: Optional[torch.device] = None,\n    ) -> \"TreeAttnMetadata\":\n        \"\"\"\n        Args:\n            tree_choices: tree description in the style of\n                https://github.com/FasterDecoding/Medusa/blob/5e9805386/medusa/model/medusa_choices.py\n                A typical tree description would look like:\n                [(node0, node1, ...), (node0, node2), (node0, node3), (node1, node3), ..., (node0, node2, ..., nodeN)]\n                Every tuple is corresponds to one node in the tree, encoded as a path from one of the root nodes to the\n                node in question.\n                For example, a node encoded as (1, 0, 3, ..., 2) is understood as:\n                list all the root nodes and take node number 1\n                list all children of that node and take node number 0\n                list all children of that node and take node number 3\n                ...\n                list all children of that node and take node number 2 - that's the node encoded by this tuple.\n\n            dtype: data type of the output mask tensor.\n            device: device of the output tensors.\n        Returns:\n            TreeAttnMetadata object with all the fields.\n        \"\"\"\n        # from https://github.com/SafeAILab/EAGLE/blob/e98fc7c/model/utils.py#L89C1-L117C1\n        sorted_tree_choices = sorted(tree_choices, key=lambda x: (len(x), x))\n        tree_len = len(sorted_tree_choices) + 1\n\n        depth_counts = _get_depth_counts(sorted_tree_choices)\n        tree_indices = _prepare_tree_indices(sorted_tree_choices, depth_counts, device)\n        retrieval_indices, path_lengths = _prepare_retrieval_indices(\n            sorted_tree_choices, device\n        )\n        tree_seq_position_ids = _prepare_tree_position_ids(\n            depth_counts, tree_len, device\n        )\n        tree_attn_mask = _prepare_tree_attn_bias(\n            sorted_tree_choices, depth_counts, dtype, device\n        )\n        parent_node_indices = _prepare_parent_node_indices(sorted_tree_choices, device)\n        child_node_indices, num_children_per_node = _prepare_child_node_indices(\n            sorted_tree_choices, device\n        )\n        candidate_idx = _prepare_candidate_idx(sorted_tree_choices, device)\n\n        num_nodes_per_level = _get_num_nodes_per_level(depth_counts, device)\n        num_nodes_per_level_cpu = num_nodes_per_level.cpu()\n        (\n            subtree_sizes,\n            num_children_per_node_at_level,\n        ) = _get_subtree_size_and_num_children_per_node_at_level(\n            num_nodes_per_level, num_children_per_node, device\n        )\n        num_children_per_node_at_level_cpu = [\n            row.cpu() for row in num_children_per_node_at_level\n        ]\n        return TreeAttnMetadata(\n            sorted_tree_choices,\n            tree_attn_mask,\n            tree_indices,\n            retrieval_indices,\n            path_lengths,\n            tree_seq_position_ids,\n            parent_node_indices,\n            child_node_indices,\n            num_children_per_node,\n            candidate_idx,\n            num_nodes_per_level,\n            num_nodes_per_level_cpu,\n            num_children_per_node_at_level,\n            num_children_per_node_at_level_cpu,\n            subtree_sizes,\n        )\n\n\ndef _get_subtree_size_and_num_children_per_node_at_level(\n    num_nodes_per_level: torch.Tensor,\n    num_children_per_node: torch.Tensor,\n    device: Optional[torch.device] = None,\n) -> Tuple[List[int], List[torch.Tensor]]:\n    depth: int = len(num_nodes_per_level)\n    subtree_sizes: List[int] = [\n        1,\n    ]\n    num_children_per_node_at_level: List[torch.Tensor] = [\n        num_children_per_node[0].unsqueeze(0)\n    ]\n    for i in range(1, depth):\n        subtree_sizes.append(int(torch.sum(num_nodes_per_level[: (i + 1)])))\n        num_children_per_node_at_level.append(\n            num_children_per_node[subtree_sizes[i - 1] : subtree_sizes[i]]\n        )\n    return subtree_sizes, num_children_per_node_at_level\n\n\ndef _get_depth_counts(sorted_tree_choices: List[Tuple[int, ...]]) -> List[int]:\n    # Initialize depth_counts to keep track of how many choices have a particular depth\n    depth_counts = []\n    prev_depth = 0\n    for path in sorted_tree_choices:\n        depth = len(path)\n        if depth != prev_depth:\n            depth_counts.append(0)\n        depth_counts[depth - 1] += 1\n        prev_depth = depth\n    return depth_counts\n\n\ndef _get_num_nodes_per_level(\n    depth_counts: List[int], device: Optional[torch.device]\n) -> torch.Tensor:\n    depth_counts_tensor: torch.Tensor = torch.tensor([1] + depth_counts, device=device)\n    return depth_counts_tensor[depth_counts_tensor != 0]\n\n\ndef _prepare_tree_attn_bias(\n    sorted_tree_choices: List[Tuple[int, ...]],\n    depth_counts: List[int],\n    dtype: Optional[torch.dtype],\n    device: Optional[torch.device],\n) -> torch.Tensor:\n    \"\"\"\n    Construct a Medusa-style tree attention bias as an explicit tensor.\n    It can be used as a spec_attn_bias (\"right\" or \"suffix\" attention part)\n    in tree_attention. See run_tree_attention_inner in test for a usage example.\n    Args:\n        sorted_tree_choices: tree description in the style of\n            https://github.com/FasterDecoding/Medusa/blob/5e9805386/medusa/model/medusa_choices.py\n            A typical tree description would look like:\n            [(node0, node1, ...), (node0, node2), (node0, node3), (node1, node3), ..., (node0, node2, ..., nodeN)]\n            Every tuple is corresponds to one node in the tree, encoded as a path from one of the root nodes to the\n            node in question. Passed in sorted order.\n            For example, a node encoded as (1, 0, 3, ..., 2) is understood as:\n            list all the root nodes and take node number 1\n            list all children of that node and take node number 0\n            list all children of that node and take node number 3\n            ...\n            list all children of that node and take node number 2 - that's the node encoded by this tuple\n        depth_counts: a list of integers, where the i-th element is the number of choices with depth i.\n        dtype: data type of the output tensor.\n        device: device of the output tensor.\n    Returns:\n        attention bias of shape (tree_size, tree_size),\n        where tree_size is the total number of nodes in the tree.\n    \"\"\"\n    # +1 comes from the addtional root node\n    tree_len = len(sorted_tree_choices) + 1\n    tree_attn_mask = torch.full(\n        (tree_len, tree_len), -torch.inf, device=device, dtype=dtype\n    )\n\n    mask_val = 0\n    for i in range(tree_len):\n        tree_attn_mask[i, i] = mask_val\n\n    tree_attn_mask[:, 0] = mask_val\n    start = 0\n    for i in range(len(depth_counts)):\n        for j in range(depth_counts[i]):\n            cur_tree_choice = sorted_tree_choices[start + j]\n            # retrieve ancestor position\n            if len(cur_tree_choice) == 1:\n                continue\n            ancestor_idx = []\n            for c in range(len(cur_tree_choice) - 1):\n                ancestor_idx.append(\n                    sorted_tree_choices.index(cur_tree_choice[: c + 1]) + 1\n                )\n            tree_attn_mask[j + start + 1, ancestor_idx] = mask_val\n        start += depth_counts[i]\n    return tree_attn_mask\n\n\ndef _prepare_tree_indices(\n    sorted_tree_choices: List[Tuple[int, ...]],\n    depth_counts: List[int],\n    device: Optional[torch.device],\n) -> torch.Tensor:\n    \"\"\"\n    Construct an index tensor for choices in the tree and their corresponding index in the draft tokens.\n    Args:\n        sorted_tree_choices: sorted from tree_choices input of prepare_tree_attn_metadata function\n        depth_counts: a list of integers, where the i-th element is the number of choices with depth i.\n        device: device of the output tensor.\n    Returns:\n        tree indices of shape (tree_len,). See docstring of TreeAttnMetadata for details.\n    \"\"\"\n    # Generate tree indices for the tree_choices structure\n    # add root node from main head prediction to the tree_len\n    tree_len = len(sorted_tree_choices) + 1\n    tree_indices = torch.zeros(tree_len, device=device, dtype=torch.long)\n    tree_indices[0] = 0\n    start, max_idx_prev_level = 0, 0\n    for i in range(len(depth_counts)):\n        cur_offset = max_idx_prev_level\n        for j in range(depth_counts[i]):\n            cur_tree_choice = sorted_tree_choices[start + j]\n            tree_idx = cur_tree_choice[-1] + cur_offset + 1\n            tree_indices[start + j + 1] = tree_idx\n            max_idx_prev_level = max(tree_idx, max_idx_prev_level)\n        start += depth_counts[i]\n    return tree_indices\n\n\ndef _prepare_retrieval_indices(\n    tree_choices: List[Tuple[int, ...]], device: Optional[torch.device]\n) -> Tuple[torch.Tensor, List[int]]:\n    \"\"\"\n    Convert tree definition from the format used by Medusa and EAGLE (tree_choices, see docstring of\n    TreeAttnMetadata.from_tree_choices) to a list of paths:\n    [\n        (node_index0_path0, node_index1_path0, ...),\n        (node_index0_path1, node_index1_path1, ...),\n        ...\n    ]\n    where each value is an index of a node inside the corresponding level of a tree.\n    Returns:\n        retrieval indices of shape (number of leaves, depth + 1)\n        length of each path.\n    \"\"\"\n    tree_depth = max(len(node) for node in tree_choices) + 1 if tree_choices else 1\n\n    leaves = set(tree_choices)\n\n    for node in tree_choices[::-1]:\n        if node[:-1] in leaves:\n            leaves.remove(node[:-1])\n\n    paths, path_lengths = [], []\n    for leaf in leaves:\n        path = [0] + [\n            tree_choices.index(leaf[:level]) + 1 for level in range(1, len(leaf) + 1)\n        ]\n        path_lengths.append(len(path))\n        paths.append(path + [-1] * (tree_depth - len(path)))\n    paths_tensor = torch.tensor(paths, dtype=torch.long, device=device)\n    return paths_tensor, path_lengths\n\n\ndef _prepare_tree_position_ids(\n    depth_counts: List[int], tree_len: int, device: Optional[torch.device]\n) -> torch.Tensor:\n    \"\"\"\n    Construct sequence position of each node within its path, can be used for positional embedding.\n    Args:\n        depth_counts: number of nodes at each of the levels of the tree.\n        tree_len: total number of nodes in the tree including the root.\n        device: device of the output tensor.\n    Returns:\n        tree position ids of shape (tree_len,). See docstring of TreeAttnMetadata for details.\n    \"\"\"\n    tree_position_ids = torch.zeros(tree_len, dtype=torch.int32, device=device)\n    start = 0\n    for i in range(len(depth_counts)):\n        tree_position_ids[start + 1 : start + depth_counts[i] + 1] = i + 1\n        start += depth_counts[i]\n\n    return tree_position_ids\n\n\ndef _prepare_parent_node_indices(\n    sorted_tree_choices: List[Tuple[int, ...]], device: Optional[torch.device]\n) -> torch.Tensor:\n    ancestor_idx = []\n    for cur_medusa_choice in sorted_tree_choices:\n        try:\n            ancestor_idx.append(sorted_tree_choices.index(cur_medusa_choice[:-1]) + 1)\n        except ValueError:\n            ancestor_idx.append(0)\n    return torch.tensor(ancestor_idx, dtype=torch.long, device=device)\n\n\ndef _prepare_child_node_indices(\n    tree_choices: List[Tuple[int, ...]], device: Optional[torch.device]\n) -> Tuple[torch.Tensor, torch.Tensor]:\n    res = []\n    num_children_per_node = []\n    for x in [()] + tree_choices:\n        curr_children = [\n            i\n            for i, y in enumerate(tree_choices)\n            if len(x) + 1 == len(y) and y[:-1] == x\n        ]\n        num_children_per_node.append(len(curr_children))\n        if curr_children:\n            res.append(curr_children)\n        else:\n            res.append([0])\n\n    # pad all children lists by repeating the last element\n    max_num_children = max(len(x) for x in res)\n    res = [x + x[-1:] * (max_num_children - len(x)) for x in res]\n\n    # Check that all nodes have the same number of children.\n    assert all(len(x) == len(res[0]) for x in res)\n    return (\n        torch.tensor(res, dtype=torch.long, device=device),\n        torch.tensor(num_children_per_node, dtype=torch.long, device=device),\n    )\n\n\ndef _prepare_candidate_idx(\n    tree_choices: List[Tuple[int, ...]], device: Optional[torch.device]\n) -> torch.Tensor:\n    candidate_idx = [\n        sum(\n            curr_node[:-1] == another_node[:-1]\n            for another_node in tree_choices[:curr_node_idx]\n        )\n        for curr_node_idx, curr_node in enumerate(tree_choices)\n    ]\n    return torch.tensor(candidate_idx, dtype=torch.long, device=device)\n\n\ndef use_triton_splitk_for_prefix(B: int, G: int, tree_size: int) -> bool:\n    \"\"\"\n    Heuristic to decide whether to use Triton Split-k or default (Flash Attention) for prefix attention.\n    \"\"\"\n    return (\n        (B * G <= 128 and tree_size <= 64)\n        or (B * G < 4 and tree_size < 100)\n        or B * G < 2\n    )\n\n\ndef select_prefix_op(\n    B: int,\n    G: int,\n    tree_size: int,\n    autotune: bool,\n    attn_bias: AttentionBias,\n    kv_cache_dtype: torch.dtype,\n) -> Optional[Type[AttentionFwOpBase]]:\n    \"\"\"\n    Heuristic to decide whether to use Triton Split-k or default (Flash Attention) for prefix attention.\n    \"\"\"\n    triton_splitk_op = SplitKAutotune if autotune else triton_splitk.FwOp\n    if torch.version.hip:\n        # TODO: further tune heuristics once CK splitK is ready\n        return triton_splitk_op\n\n    # Triton Split-k is not present in the dispatcher list for some shapes.\n    # However, we need to dispatch to it if no other op is available.\n    # FA3 splitKV doesn't yet support gappy or paged biases.\n    fa3_splitkv_supported = isinstance(\n        attn_bias, flash3.FwOp_KVSplit.SUPPORTED_ATTN_BIAS_TYPES  # type: ignore\n    )\n    fa3_supported = isinstance(attn_bias, flash3.FwOp.SUPPORTED_ATTN_BIAS_TYPES)  # type: ignore\n    flash2_supported = isinstance(attn_bias, flash.FwOp.SUPPORTED_ATTN_BIAS_TYPES)  # type: ignore\n    if not (fa3_splitkv_supported or fa3_supported or flash2_supported):\n        return triton_splitk_op\n\n    assert torch.version.cuda\n    use_fa3 = _get_use_fa3() and fa3_available()\n    # override heuristics for quantized kv cache for decode\n    if kv_cache_dtype == torch.uint8:\n        return triton_splitk_op\n    # select FA3 when bs >= 64\n    if B >= 64 and use_fa3:\n        if fa3_splitkv_supported:\n            return flash3.FwOp_KVSplit\n        return flash3.FwOp\n    elif use_triton_splitk_for_prefix(B, G, tree_size):\n        return triton_splitk_op\n    else:\n        # use default heuristics from xformers\n        return None\n\n\ndef tree_attention(\n    q: torch.Tensor,\n    spec_k: torch.Tensor,\n    spec_v: torch.Tensor,\n    cache_k: torch.Tensor,\n    cache_v: torch.Tensor,\n    spec_attn_bias: torch.Tensor,\n    prefix_attn_bias: AttentionBias,\n    prefix_op: Optional[Type[AttentionFwOpBase]] = None,\n    suffix_op: Optional[Type[AttentionFwOpBase]] = None,\n    autotune: bool = False,\n    quantized_kv_scales: Optional[Tuple[torch.Tensor, torch.Tensor]] = None,\n) -> torch.Tensor:\n    \"\"\"\n    Compute Medusa/EAGLE/Hydra-style tree attention.\n    Notice that this function takes as arguments biases for the left (prefix)\n    and right (speculative suffix) parts of the attention.\n    This way we avoid creating these biases on the fly, and\n    allow this function to be used in performance-critical decoding\n    jobs, including in CUDA graph mode. In the latter case one should\n    construct the biases once, and update prefix_attn_bias with\n    current seqlens before every graph replay; spec_attn_bias stays static,\n    as it's determined by the tree structure.\n    Args:\n        q: query from speculative tokens, of shape (B, tree_size_q, (G), H, D)\n        spec_k, spec_v: keys/values from speculative tokens, each of shape (B, tree_size_kv, (G), H, D).\n            If tree_size_q < tree_size_kv, we assume the end of the query sequence aligns with end the k/v sequence,\n            like in \"from-bottom-right\" attention masks. Such rectangular attention masks can be used when we are\n            adding new nodes to the tree, and want to avoid recomputing attention for the existing nodes. For example,\n            this can be used during draft token generation in EAGLE.\n        cache_k/cache_v: queries/keys/values from the existing context, each of shape (B, Mk, (G), H, D)\n        spec_attn_bias: attention bias of the \"right\" part of the attention (tree_size_q x spec tokens).\n            This would typically be a an explicit tensor mask, precomputed once and not changing during decoding\n        prefix_attn_bias: attention bias of the \"left\" part of the attention (tree_size_q x existing context).\n            This bias would typically be block-diagonal padded non-causal (BlockDiagonalPaddedKeysMask), and it\n            changes at every decoding step as K/V sequence lengths grow during decoding.\n        prefix_op: attention backend which will be passed to memory_efficient_attention to compute prefix attention.\n                   If None, will use Triton Split-K or Flash Attention depending on the heuristics.\n        suffix_op: same as prefix_op, but for the suffix.\n        autotune: If True, Triton Split-K will use autotuning when chosen\n            as a default backend for prefix/suffix attention.\n    Returns:\n        attention output of shape (B, tree_size_q, (G), H, D)\n\n    :Usage example:\n\n        See also tree_attention_with_sync in tests/test_tree_attention.py\n\n    .. code-block:: python\n\n        # Create an attention bias for the prefix part of the attention\n        prefix_attn_bias = BlockDiagonalPaddedKeysMask.from_seqlens(\n            q_seqlen=[tree_size_q for _ in range(B)], kv_seqlen=kv_lens, kv_padding=Mk\n        )\n        # Create an explit attention bias for the speculative part of the attention\n        spec_attn_bias = TreeAttnMetadata.from_tree_choices(tree_choices, q.dtype, q.device).attention_bias\n        attn_output = tree_attention(\n            q, spec_k, spec_v, cache_k, cache_v, spec_attn_bias, prefix_attn_bias\n        )\n    \"\"\"\n\n    is_bmhk = q.ndim == 4\n    if is_bmhk:\n        q = q.unsqueeze(2)\n        spec_k, spec_v = spec_k.unsqueeze(2), spec_v.unsqueeze(2)\n        cache_k, cache_v = cache_k.unsqueeze(2), cache_v.unsqueeze(2)\n\n    B, tree_size_q, G, H, D = q.shape\n    Bkv, Mk, G1, H1, D1 = cache_k.shape\n    tree_size_q1, tree_size_kv = spec_attn_bias.shape\n    if isinstance(\n        prefix_attn_bias,\n        (PagedBlockDiagonalPaddedKeysMask, PagedBlockDiagonalGappyKeysMask),\n    ):\n        assert Bkv == 1\n    else:\n        assert Bkv == B\n\n    assert H == H1 and D == D1 and G == G1\n    assert cache_k.shape == cache_v.shape\n    assert (\n        tree_size_q1 == tree_size_q <= tree_size_kv\n    ), f\"{tree_size_q1=} {tree_size_q=} {tree_size_kv=}\"\n    assert (\n        q.shape[2:] == spec_k.shape[2:] == spec_v.shape[2:]\n    ), f\"{q.shape=} {spec_k.shape=} {spec_v.shape=}\"\n\n    spec_attn_bias = spec_attn_bias.expand(B, G, H, tree_size_q, tree_size_kv)\n\n    triton_splitk_op = SplitKAutotune if autotune else triton_splitk.FwOp\n\n    # TODO: improve this heuristic\n    if prefix_op is None:\n        prefix_op = select_prefix_op(\n            B, G, tree_size_kv, autotune, prefix_attn_bias, cache_k.dtype\n        )\n    if cache_k.dtype == torch.uint8:\n        assert quantized_kv_scales is not None\n        assert prefix_op is triton_splitk.FwOp\n        fp8_inp = triton_splitk.InputsFp8(\n            query=q.view(1, B * tree_size_q, G, H, D),\n            key=cache_k.view(1, Bkv * Mk, G, H, D).view(torch.int32),\n            value=cache_v.view(1, Bkv * Mk, G, H, D).view(torch.int32),\n            attn_bias=prefix_attn_bias,\n            k_fp8_scale_shift=quantized_kv_scales[0].view(1, Bkv * Mk, G, H),\n            v_fp8_scale_shift=quantized_kv_scales[1].view(1, Bkv * Mk, G, H),\n            is_partial=True,\n        )\n        out, ctx = fmha._memory_efficient_attention_forward_requires_grad(\n            fp8_inp,\n            op=prefix_op,\n        )\n        attn_prefix, lse_prefix = out, ctx.lse\n    else:\n        attn_prefix, lse_prefix = memory_efficient_attention_partial(\n            q.view(1, B * tree_size_q, G, H, D),\n            cache_k.view(1, Bkv * Mk, G, H, D),\n            cache_v.view(1, Bkv * Mk, G, H, D),\n            attn_bias=prefix_attn_bias,\n            op=prefix_op,\n        )\n    attn_prefix = attn_prefix.view(B, tree_size_q, G, H, D)\n    lse_prefix = lse_prefix.view(G, H, B, tree_size_q).permute(2, 0, 1, 3)\n\n    # attn_suffix ~ (B, tree_size_q, G, H, D)\n    # lse_suffix ~ (B, G, H, tree_size_q)\n    attn_suffix, lse_suffix = memory_efficient_attention_forward_requires_grad(\n        q,\n        spec_k,\n        spec_v,\n        attn_bias=spec_attn_bias,\n        op=suffix_op or triton_splitk_op,\n    )\n\n    # attn_output ~ [B, tree_size_q, G, H, D]\n    # attn input [B, M, G, H, Kq]\n    # lse input [B, G, H, M]\n    attn_output, _ = merge_attentions(\n        [attn_prefix, attn_suffix],\n        [lse_prefix, lse_suffix],\n        output_dtype=q.dtype,\n    )\n\n    if is_bmhk:\n        attn_output = attn_output.squeeze(2)\n    return attn_output\n\n\nclass SplitKAutotune(triton_splitk.FwOp):\n    AUTOTUNE = True\n\n\n@lru_cache\ndef construct_full_tree_choices(\n    tree_depth: int, branching: int\n) -> List[Tuple[int, ...]]:\n    \"\"\"\n    Construct a full tree of a given depth where each node (except for leaves) has a given number of children.\n    The format is compatible with that used by Medusa and EAGLE:\n    https://github.com/FasterDecoding/Medusa/blob/5e98053/medusa/model/medusa_choices.py\n    For detailed description, see docstring of\n    xformers.ops.tree_attention.TreeAttnMetadata.from_tree_choices .\n    \"\"\"\n    return construct_tree_choices(branching=[branching] * tree_depth)\n\n\ndef construct_tree_choices(\n    branching: List[int],\n) -> List[Tuple[int, ...]]:\n    \"\"\"\n    Construct a tree based on given branching factor for each non-root level.\n    \"\"\"\n    choices: List[Tuple[int, ...]] = []\n    for i in range(len(branching)):\n        choices.extend(itertools.product(*[range(branching[k]) for k in range(i + 1)]))\n    return choices\n\n\ndef get_full_tree_size(tree_depth: int, branching: int) -> int:\n    \"\"\"\n    Number of nodes in a full tree of a given depth (including the root node) and branching factor.\n    \"\"\"\n    return sum(branching**i for i in range(tree_depth))\n"
  },
  {
    "path": "xformers/ops/unbind.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import List, Optional, Sequence, Tuple, Union\n\nimport torch\n\nfrom .common import _get_storage_base\n\n\ndef get_stack_strides(\n    tensors: Sequence[torch.Tensor], dim: int\n) -> Optional[Tuple[Union[int, torch.SymInt], ...]]:\n    \"\"\"\n    If the tensors are already stacked on dimension :code:`dim`, \\\n        returns the strides of the stacked tensors. \\\n        Otherwise returns :code:`None`.\n    \"\"\"\n    if len(tensors) <= 1 or dim > tensors[0].ndim:\n        return None\n\n    final_stride = []\n    for i in range(tensors[0].ndim + 1):\n        if i == dim:\n            # PyTorch 2.5 messed up the type annotations for SymInt, but 2.6 will fix it\n            # https://github.com/pytorch/pytorch/issues/138478\n            final_stride.append(\n                tensors[1].storage_offset() - tensors[0].storage_offset()  # type: ignore[operator]\n            )\n            continue\n        if i > dim:\n            i -= 1\n        final_stride.append(tensors[0].stride(i))\n\n    storage_data_ptr: Optional[int] = None\n    for i, x in enumerate(tensors[1:]):\n        # Sanity checks\n        if x.shape != tensors[0].shape:\n            return None\n        if x.stride() != tensors[0].stride():\n            return None\n        # PyTorch 2.5 messed up the type annotations for SymInt, but 2.6 will fix it\n        # https://github.com/pytorch/pytorch/issues/138478\n        if (\n            x.storage_offset()\n            != tensors[0].storage_offset() + (i + 1) * final_stride[dim]  # type: ignore[operator]\n        ):\n            return None\n        if storage_data_ptr is None:\n            storage_data_ptr = _get_storage_base(tensors[0])\n        # Actual storage check\n        if _get_storage_base(x) != storage_data_ptr:\n            return None\n    return tuple(final_stride)\n\n\ndef _stack_or_none_fw(\n    tensors: Union[Tuple[torch.Tensor, ...], List[torch.Tensor]],\n    dim: int,\n) -> Optional[torch.Tensor]:\n    strides = get_stack_strides(tensors, dim)\n    if strides is not None:\n        input_shape = list(tensors[0].shape)\n        input_shape.insert(dim, len(tensors))\n        return tensors[0].as_strided(input_shape, strides)\n    return None\n\n\ndef _stack_fw(\n    tensors: Union[Tuple[torch.Tensor, ...], List[torch.Tensor]],\n    dim: int,\n) -> torch.Tensor:\n    out = _stack_or_none_fw(tensors, dim)\n    if out is None:\n        out = torch.stack(tensors, dim=dim)\n    return out\n\n\nclass _Unbind(torch.autograd.Function):\n    \"\"\"\n    See function `unbind`\n    \"\"\"\n\n    @staticmethod\n    # type: ignore\n    def forward(ctx, x: torch.Tensor, dim: int):\n        ctx.dim = dim\n        return x.unbind(dim)\n\n    @classmethod\n    # type: ignore\n    def backward(cls, ctx, *tensors: torch.Tensor):\n        return _stack_fw(tensors, ctx.dim), None\n\n\nclass _StackOrNone(torch.autograd.Function):\n    \"\"\"\n    See function `stack_or_none`\n    \"\"\"\n\n    @staticmethod\n    # type: ignore\n    def forward(ctx, dim: int, *tensors: torch.Tensor):\n        ctx.dim = dim\n        return _stack_or_none_fw(tensors, dim=dim)\n\n    @classmethod\n    # type: ignore\n    def backward(cls, ctx, grad: torch.Tensor):\n        return (None, *grad.unbind(dim=ctx.dim))\n\n\ndef unbind(x: torch.Tensor, dim: int) -> Tuple[torch.Tensor, ...]:\n    \"\"\"\n    Does exactly the same as :attr:`torch.unbind` for the forward.\n    In backward, avoids a :attr:`torch.cat` if the gradients\n    are already multiple views of the same storage\n    \"\"\"\n    return _Unbind.apply(x, dim)\n\n\ndef stack_or_none(tensors: Sequence[torch.Tensor], dim: int) -> torch.Tensor:\n    \"\"\"\n    Does exactly the same as :attr:`torch.stack` if the tensors can be concatenated\n    without any memory operation. Otherwise returns None.\n    \"\"\"\n    return _StackOrNone.apply(dim, *tensors)\n"
  },
  {
    "path": "xformers/profiler/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom .api import profile, step\nfrom .profiler import MemSnapshotsProfiler, NsightProfiler, PyTorchProfiler\n\n__all__ = [\n    \"profile\",\n    \"step\",\n    \"MemSnapshotsProfiler\",\n    \"PyTorchProfiler\",\n    \"NsightProfiler\",\n]\n"
  },
  {
    "path": "xformers/profiler/api.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom typing import Any, Optional, Sequence, Tuple\n\nimport torch.nn as nn\n\nfrom .profiler import (\n    _Profiler,\n    MemSnapshotsProfiler,\n    NsightProfiler,\n    PyTorchProfiler,\n    PyTorchProfiler_CUDAOnly,\n)\nfrom .profiler_dcgm import DCGMProfiler  # noqa: F401\n\nDEFAULT_SCHEDULE = (\n    (MemSnapshotsProfiler, 0, 2),\n    (NsightProfiler, 4, 6),\n    (PyTorchProfiler, 6, 7),\n    (PyTorchProfiler_CUDAOnly, 7, 8),\n    # TODO: Found issues where this can take minutes to\n    # start, as it flushes previous values\n    # (DCGMProfiler, 9, 11),\n)\n\n\ndef profile(\n    output_dir: str,\n    module: Optional[nn.Module] = None,\n    schedule: Sequence[Tuple[Any, int, int]] = DEFAULT_SCHEDULE,\n):\n    \"\"\"\n    A pre-configured profiler that will run on the first ~20 steps of the training\n    It will provide multiple traces that can be exploited later.\n    Use it in a context manager around your training loop, and call `xformers.profiler.step`\n    before starting the next iteration.\n\n    :Examples:\n\n    .. code-block:: python\n\n        import torch\n        import timm.models\n        import xformers.profiler\n\n        dtype = torch.bfloat16\n        device = \"cuda\"\n        model = timm.models.vit_large_patch16_224().to(device).to(dtype)\n        inp = torch.zeros([64, 3, 224, 224], device=device, dtype=dtype)\n        optim = torch.optim.Adam(model.parameters())\n\n        with xformers.profiler.profile(\n            output_dir=\"profile_data\",\n            module=model,\n            schedule=[\n                (MemSnapshotsProfiler, 0, 2),\n                (NsightProfiler, 4, 6),\n                (PyTorchProfiler, 6, 20),\n            ]\n        ):\n            for i in range(20):\n                model(inp).sum().backward()\n                optim.step()\n                optim.zero_grad()\n                xformers.profiler.step()\n\n        # alternatively, use the profiler without context and with ``.start()`` / `.stop()`\n        # calls.\n\n        xprofiler = xformers.profiler.profile(...)\n        xprofiler.start()\n\n        for i in range(20):\n            model(inp).sum().backward()\n            optim.step()\n            optim.zero_grad()\n            xprofiler.step()\n\n        xprofiler.stop()\n    \"\"\"\n    return _Profiler(output_dir=output_dir, schedule=schedule, module=module)\n\n\ndef step() -> None:\n    \"\"\"See `xformers.profiler.profile`\"\"\"\n    # Silently return if no profiler is enabled\n    if _Profiler._CURRENT_PROFILER is None:\n        return\n    _Profiler._CURRENT_PROFILER.step()\n"
  },
  {
    "path": "xformers/profiler/device_limits.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nfrom dataclasses import dataclass, field\nfrom typing import Mapping, Optional, Tuple\n\nimport torch\n\n\n@dataclass\nclass DeviceLimit:\n    name: str = \"default\"  # pattern to match from `torch.cuda.get_device_name()`\n    source: str = \"\"\n    sm: Tuple[int, int] = (0, 0)\n    # bytes/s\n    gmem_bandwidth: float = math.inf\n    # dtype -> TFlop/s\n    gemm_tflops: Mapping[torch.dtype, float] = field(default_factory=dict)\n\n\n# For f32, we assume we can use tf32\nDEVICE_LIMITS: Tuple[DeviceLimit, ...] = (\n    DeviceLimit(\n        \"H100\",\n        \"https://resources.nvidia.com/en-us-tensor-core/nvidia-tensor-core-gpu-datasheet\",  # noqa: E501\n        sm=(9, 0),\n        gmem_bandwidth=3.35 * (1024**4),  # NOTE: PCIe is 2 TB/s\n        gemm_tflops={\n            torch.float64: 67,\n            # NOTE: NVIDIA gives all numbers \"with 2:4 sparsity\"\n            # but we want the full GEMM numbers\n            torch.float32: 989 // 2,\n            torch.float16: 1979 // 2,\n            torch.bfloat16: 1979 // 2,\n            torch.int8: 3958 // 2,\n        },\n    ),\n    DeviceLimit(\n        \"A100\",\n        \"https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/nvidia-a100-datasheet-us-nvidia-1758950-r4-web.pdf\",  # noqa: E501\n        sm=(8, 0),\n        gmem_bandwidth=2 * (1024**4),  # NOTE: PCIe is 1.5 TB/s\n        gemm_tflops={\n            torch.float64: 19.5,\n            torch.float32: 156,\n            torch.float16: 312,\n            torch.bfloat16: 312,\n            torch.int8: 624,\n        },\n    ),\n    DeviceLimit(\n        \"A30\",\n        \"https://www.nvidia.com/content/dam/en-zz/Solutions/data-center/products/a30-gpu/pdf/a30-datasheet.pdf\",\n        sm=(8, 0),\n        gmem_bandwidth=933 * (1024**3),\n        gemm_tflops={\n            torch.float64: 10.3,\n            torch.float32: 82,\n            torch.float16: 165,\n            torch.bfloat16: 165,\n            torch.int8: 330,\n        },\n    ),\n    DeviceLimit(\n        \"T4\",\n        \"https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/tesla-t4/t4-tensor-core-datasheet-951643.pdf\",\n        sm=(7, 5),\n        gmem_bandwidth=300 * (1024**3),\n        gemm_tflops={\n            torch.float32: 8.1,\n            torch.float16: 65,\n            torch.int8: 130,\n        },\n    ),\n    # Assuming SXM2\n    DeviceLimit(\n        \"V100\",\n        \"https://images.nvidia.com/content/technologies/volta/pdf/tesla-volta-v100-datasheet-letter-fnl-web.pdf\",\n        sm=(7, 0),\n        gmem_bandwidth=900 * (1024**3),\n        gemm_tflops={\n            torch.float64: 7.8,\n            torch.float32: 15.7,\n            torch.float16: 125,\n        },\n    ),\n    DeviceLimit(\n        \"P100\",\n        \"https://images.nvidia.com/content/tesla/pdf/nvidia-tesla-p100-datasheet.pdf\",\n        sm=(6, 0),\n        gmem_bandwidth=732 * (1024**3),\n        gemm_tflops={\n            torch.float64: 5.3,\n            torch.float32: 10.6,\n            torch.float16: 21.2,\n        },\n    ),\n)\n\n\ndef get_device_limits(device) -> Optional[DeviceLimit]:\n    \"\"\"Currently only implemented for GPUs\"\"\"\n    if device is not None and device.type == \"cuda\":\n        device_sm = torch.cuda.get_device_capability(device)\n        device_name = torch.cuda.get_device_name(device)\n        for lim in DEVICE_LIMITS:\n            if lim.sm == device_sm:\n                if lim.name in device_name:\n                    return lim\n    return None\n"
  },
  {
    "path": "xformers/profiler/find_slowest.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport argparse\nimport concurrent.futures\nimport glob\nimport os\nimport subprocess\nfrom shlex import quote\nfrom typing import Dict, List\n\nimport pandas as pd\n\n\ndef print_json_as_dataframe(json_list):\n    if not json_list:\n        print(\"Empty list\")\n        return\n\n    # Extract the headers from the keys of the first dictionary\n    headers = list(json_list[0].keys())\n\n    # Determine the width of each column\n    col_widths = {header: max(len(header), 10) for header in headers}\n    for row in json_list:\n        for header in headers:\n            col_widths[header] = max(col_widths[header], len(str(row[header])))\n\n    # Create the header row\n    header_row = \"  \".join(f\"{header:<{col_widths[header]}}\" for header in headers)\n    print(header_row)\n    print(\"-\" * len(header_row))\n\n    # Create each data row\n    for row in json_list:\n        data_row = \"  \".join(\n            f\"{str(row[header]):<{col_widths[header]}}\" for header in headers\n        )\n        print(data_row)\n\n\ndef compute_std_dev_of_event_durations_over_ranks(events, top=5):\n    grouped_sorted_events = list(\n        events.filter(items=[\"name\", \"log_name\", \"dur\"])\n        .groupby([\"name\", \"log_name\"])\n        .sum()\n        .groupby([\"name\"])\n        .std()\n        .sort_values([\"dur\"], ascending=False)\n        .iterrows()\n    )\n\n    return [\n        {\"name\": idx, \"std_dev\": f\"{row.dur / 1000:.2f} ms\"}\n        for idx, row in grouped_sorted_events[:top]\n    ]\n\n\ndef sort_nccl_events(\n    nccl_events, top_k: int = 3, last_k: int = 3\n) -> List[Dict[str, str]]:\n    grouped_sorted_events = list(\n        nccl_events.filter(items=[\"log_name\", \"dur\"])\n        .groupby([\"log_name\"])\n        .sum()\n        .sort_values([\"dur\"])\n        .iterrows()\n    )\n\n    return [\n        {\"log_name\": idx, \"nccl_ms\": f\"{row.dur / 1000:.2f} ms\"}\n        for idx, row in (\n            grouped_sorted_events[:top_k] + grouped_sorted_events[-last_k:]\n        )\n    ]\n\n\ndef read_one_file(profile_trace_path: str) -> pd.DataFrame:\n    if profile_trace_path.endswith(\".csv\"):\n        return pd.read_csv(profile_trace_path, names=[\"name\", \"dur\"])\n\n    jq_pipe = '.traceEvents[] | select(.cat == \"kernel\") | [.name, .dur] | @csv'\n    if profile_trace_path.endswith(\".gz\"):\n        cmd = (\n            f\"gunzip -c {quote(profile_trace_path)} | jq --raw-output {quote(jq_pipe)}\"\n        )\n    else:\n        cmd = f\"jq --raw-output {quote(jq_pipe)} {quote(profile_trace_path)}\"\n\n    subp = subprocess.Popen(\n        cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL\n    )\n    try:\n        kernel_events = pd.read_csv(subp.stdout, names=[\"name\", \"dur\"])\n    except Exception:\n        subp.terminate()\n        raise\n    finally:\n        assert subp.wait() == 0\n\n    return kernel_events\n\n\ndef parse_one_file(profile_trace_path: str) -> tuple[pd.DataFrame, pd.DataFrame]:\n    kernel_events = read_one_file(profile_trace_path)\n    kernel_events[\"log_name\"] = os.path.basename(profile_trace_path)\n\n    communication_kernels = kernel_events[kernel_events.name.str.startswith(\"nccl\")]\n    computation_kernels = kernel_events[~(kernel_events.name.str.startswith(\"nccl\"))]\n\n    return communication_kernels, computation_kernels\n\n\ndef print_profiling_info(cuda_profile_dir: str):\n    cuda_profile_path_name = f\"{cuda_profile_dir}/kernels_*.csv\"\n    profile_files = glob.glob(cuda_profile_path_name)\n\n    if len(profile_files) == 0:\n        cuda_profile_path_name = f\"{cuda_profile_dir}/*.pt.trace.json.gz\"\n        profile_files = glob.glob(cuda_profile_path_name)\n\n    if len(profile_files) == 0:\n        cuda_profile_path_name = f\"{cuda_profile_dir}/*.json\"\n        profile_files = glob.glob(cuda_profile_path_name)\n\n    if len(profile_files) == 0:\n        raise Exception(\n            f\"Couldnt find any profiling trace in the specified directory: {cuda_profile_dir}\"\n        )\n\n    # Extract detailed NCCL event durations for all logs\n    communication_kernels = []\n    computation_kernels = []\n    with concurrent.futures.ThreadPoolExecutor(max_workers=32) as executor:\n        for index, (comm_ks, comp_ks) in enumerate(\n            executor.map(parse_one_file, profile_files)\n        ):\n            print(\n                f\"Processed file {index + 1}/{len(profile_files)}\", end=\"\\r\", flush=True\n            )\n            communication_kernels.append(comm_ks)\n            computation_kernels.append(comp_ks)\n    communication_kernels = pd.concat(communication_kernels)\n    computation_kernels = pd.concat(computation_kernels)\n    print()\n\n    print(\"The longest and shortest communication_kernels:\")\n    print_json_as_dataframe(sort_nccl_events(communication_kernels))\n    print(\"\\n\\n\")\n\n    std_df = compute_std_dev_of_event_durations_over_ranks(communication_kernels)\n    print(\"The standard deviation of nccl kernels durations across ranks:\")\n    print_json_as_dataframe(std_df)\n    print(\"\\n\\n\")\n\n    std_df = compute_std_dev_of_event_durations_over_ranks(computation_kernels)\n    print(\"The standard deviation of computation kernels durations across ranks:\")\n    print_json_as_dataframe(std_df)\n\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Process CUDA profile directory.\")\n    parser.add_argument(\"cuda_profile_dir\", type=str, help=\"The CUDA profile directory\")\n\n    args = parser.parse_args()\n\n    print_profiling_info(args.cuda_profile_dir)\n"
  },
  {
    "path": "xformers/profiler/profile_analyzer.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport math\nfrom collections import defaultdict\nfrom dataclasses import dataclass\nfrom typing import Any, cast, Dict, List, Optional, Sequence\n\nimport torch\n\n\nclass FakeKinetoEvent:\n    def __init__(self, e: torch._C._autograd._KinetoEvent) -> None:\n        for attr in dir(e):\n            if attr.startswith(\"_\"):\n                continue\n            setattr(self, attr, getattr(e, attr))\n        self._kineto_event = e\n\n\ndef _attention_flops(queries, values, causal: bool, fmt: str = \"BHMK\") -> int:\n    assert isinstance(causal, bool)\n    assert fmt in [\"BMHK\", \"BHMK\"]\n    if fmt == \"BMHK\":\n        queries, values = [[x[0], x[2], x[1], x[3]] for x in [queries, values]]\n    *B, N, K = queries\n    *B, Nv, Kv = values\n    if causal:  # NOTE: Causal from bottom right\n        # non-causal part\n        flops = 2 * N * max(Nv - N, 0) * K + 2 * max(Nv - N, 0) * max(Nv - N, 0) * Kv\n        # causal part\n        flops += (\n            2 * min(N, Nv) * min(N, Nv) * K + 2 * min(N, Nv) * min(N, Nv) * Kv\n        ) // 2\n    else:\n        flops = 2 * N * Nv * K + 2 * N * Nv * Kv\n    for b in B:\n        flops *= b\n    return int(flops)\n\n\ndef _get_arg_idx(op, *arg_names: str) -> int:\n    for i, arg in enumerate(op.default._schema.arguments):\n        if arg.name in arg_names:\n            return i\n    raise ValueError(f\"No such argument {arg_names} found in {op.default._schema}\")\n\n\ndef _replace_if_needed(\n    e: torch._C._autograd._KinetoEvent,\n) -> torch._C._autograd._KinetoEvent:\n    \"\"\"\n    Adds a flops amount for operators that don't have this information in Kineto already\n    This mostly applies for the attention for now, as GEMMs are already calculated by Kineto\n    and other operations are negligible.\n    \"\"\"\n    if e.device_type().name != \"CPU\":\n        return e\n    op_name = e.name()\n    flops = None\n\n    FMT_BMHK = dict(fmt=\"BMHK\")\n    ATTN_OPS = {\n        getattr(lib, op).default.name(): (getattr(lib, op), is_bwd, kwargs)\n        for lib, op, is_bwd, kwargs in [\n            (torch.ops.aten, \"scaled_dot_product_attention\", False, {}),\n            (torch.ops.xformers_flash, \"flash_fwd\", False, FMT_BMHK),\n            (\n                torch.ops.xformers,\n                \"efficient_attention_forward_cutlass\",\n                False,\n                FMT_BMHK,\n            ),\n            (torch.ops.aten, \"_efficient_attention_forward\", False, FMT_BMHK),\n            (torch.ops.aten, \"_scaled_dot_product_flash_attention_backward\", True, {}),\n            (\n                torch.ops.aten,\n                \"_scaled_dot_product_efficient_attention_backward\",\n                True,\n                {},\n            ),\n            (torch.ops.xformers_flash, \"flash_bwd\", True, FMT_BMHK),\n            (\n                torch.ops.xformers,\n                \"efficient_attention_backward_cutlass\",\n                True,\n                FMT_BMHK,\n            ),\n            (torch.ops.aten, \"_efficient_attention_backward\", True, FMT_BMHK),\n            (torch.ops.aten, \"_scaled_dot_product_cudnn_attention_backward\", True, {}),\n        ]\n        if hasattr(lib, op)\n    }\n    if op_name in ATTN_OPS.keys():\n        op, is_bwd, kwargs = ATTN_OPS[op_name]\n        shapes = e.shapes()\n        concrete_inputs = e.concrete_inputs()\n        try:\n            is_causal = concrete_inputs[_get_arg_idx(op, \"causal\", \"is_causal\")]\n        except ValueError:\n            is_causal = concrete_inputs[_get_arg_idx(op, \"custom_mask_type\")] != 0\n        flops = _attention_flops(\n            shapes[_get_arg_idx(op, \"query\")],\n            shapes[_get_arg_idx(op, \"value\")],\n            is_causal,\n            **kwargs,\n        )\n        if is_bwd:\n            flops = flops * 5 // 2\n    if flops is not None:\n        new_e = FakeKinetoEvent(e)\n        new_e.flops = lambda: flops  # type: ignore\n        e = cast(torch._C._autograd._KinetoEvent, new_e)\n    return e\n\n\n@dataclass\nclass AnalyzedTrace:\n    operations_per_dtype_fw: Dict[torch.dtype, float]\n    operations_per_dtype_bw: Dict[torch.dtype, float]\n    total_time_s: float\n\n    def compute_num_ops(\n        self, dtype: torch.dtype, fw: bool = True, bw: bool = True\n    ) -> float:\n        ops = 0.0\n        if fw:\n            ops += self.operations_per_dtype_fw.get(dtype, 0.0)\n        if bw:\n            ops += self.operations_per_dtype_bw.get(dtype, 0.0)\n        return ops\n\n    def compute_hfu(self, hardware_flops: Dict[torch.dtype, float]) -> float:\n        hfu_seconds = 0.0\n        for dtype, hw_flops in hardware_flops.items():\n            hfu_seconds += self.compute_num_ops(dtype) / hw_flops\n        return hfu_seconds / self.total_time_s\n\n    def compute_mfu(self, hardware_flops: Dict[torch.dtype, float]) -> float:\n        # Estimated by considering the bw flops should be exactly 2x the fw flops\n        # The reason MFU!=HFU is because of recomputation in the BW pass\n        hfu_seconds = 0.0\n        for dtype, hw_flops in hardware_flops.items():\n            hfu_seconds += (\n                min(\n                    3 * self.compute_num_ops(dtype, bw=False),\n                    self.compute_num_ops(dtype),\n                )\n                / hw_flops\n            )\n        return hfu_seconds / self.total_time_s\n\n    @staticmethod\n    def _find_all_root_events_with_flops(\n        all_events: Sequence[torch._C._autograd._KinetoEvent],\n    ) -> Sequence[torch._C._autograd._KinetoEvent]:\n        # Filters-out non-dispatch ops\n        # Or operations without flop counted\n        all_ops_with_flops = [\n            e\n            for e in all_events\n            if (\n                e.device_type().name == \"CPU\"\n                and (e.dtypes() or e.shapes())\n                and e.flops() > 0\n            )\n        ]\n        events_per_group: Dict[Any, List[torch._C._autograd._KinetoEvent]] = (\n            defaultdict(list)\n        )\n        for e in all_ops_with_flops:\n            events_per_group[(e.start_thread_id(), e.device_type())].append(e)\n        root_events: List[torch._C._autograd._KinetoEvent] = []\n        for events in events_per_group.values():\n            # We assume that 2 events are either non-overlapping,\n            # or one is contained entirely within the other\n            events.sort(key=lambda e: (e.start_ns(), -e.duration_ns()))\n            current_root: Optional[torch._C._autograd._KinetoEvent] = None\n            for e in events:\n                if (\n                    current_root is None\n                    or e.start_ns()\n                    > current_root.start_ns() + current_root.duration_ns()\n                ):\n                    current_root = e\n                    root_events.append(e)\n        return root_events\n\n    @staticmethod\n    def from_profile(\n        events: Sequence[torch._C._autograd._KinetoEvent],\n    ) -> \"AnalyzedTrace\":\n        events = [_replace_if_needed(e) for e in events]\n        root_ops = AnalyzedTrace._find_all_root_events_with_flops(events)\n\n        operations_per_dtype_fw: Dict[torch.dtype, float] = defaultdict(float)\n        operations_per_dtype_bw: Dict[torch.dtype, float] = defaultdict(float)\n        # We detect BW pass ops based on their thread id\n        all_bw_threads = {e.start_thread_id() for e in events if e.fwd_thread_id() > 0}\n        # Find total dt\n        ATEN_DTYPES = [\n            # NOTE: A single torch.dtype per number of bits\n            # (eg so we map bf16 --> b16)\n            (\"double\", torch.float64),\n            (\"float\", torch.float),\n            (\"c10::Half\", torch.float16),\n            (\"c10::BFloat16\", torch.float16),\n            (\"c10::Int8\", torch.int8),\n        ]\n        begin_ns, end_ns = math.inf, 0\n        for op in root_ops:\n            dtype = None\n            for aten_dtype, torch_dtype in ATEN_DTYPES:\n                if aten_dtype in op.dtypes():\n                    dtype = torch_dtype\n                    break\n            if dtype is None:  # ???\n                continue\n            if op.start_thread_id() in all_bw_threads:\n                operations_per_dtype_bw[dtype] += op.flops()\n            else:\n                operations_per_dtype_fw[dtype] += op.flops()\n        for op in events:\n            if op.device_type().name != \"CUDA\":\n                continue\n            begin_ns = min(begin_ns, op.start_ns())\n            end_ns = max(end_ns, op.start_ns() + op.duration_ns())\n\n        return AnalyzedTrace(\n            operations_per_dtype_fw=operations_per_dtype_fw,\n            operations_per_dtype_bw=operations_per_dtype_bw,\n            total_time_s=(end_ns - begin_ns) / (10**9),\n        )\n"
  },
  {
    "path": "xformers/profiler/profiler.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport csv\nimport logging\nimport os\nimport queue\nimport socket\nimport time\nimport weakref\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom typing import Any, Dict, List, Optional, Sequence, Tuple\n\nimport torch.cuda.memory\nimport torch.cuda.nvtx\nimport torch.distributed as dist\nimport torch.nn as nn\nimport torch.profiler\n\nfrom .device_limits import get_device_limits\nfrom .profile_analyzer import AnalyzedTrace\n\nlogger = logging.getLogger(__name__)\n\n\nclass NsightProfiler:\n    \"\"\"Profiler that triggers start of NSight profiler.\n\n    NOTE: you need to ensure that the script running this code actually is running with\n    ``nsys profile`` and also has a flag ``--capture-range=cudaProfilerApi`` so the\n    capturing is performed by this profiler during certain steps.\n    \"\"\"\n\n    def __init__(self, main_profiler: \"_Profiler\") -> None:\n        self.main_profiler = main_profiler\n        # TODO figure out if there is a way to know if nsys is launched at this point\n\n    def __enter__(self):\n        torch.cuda.profiler.start()\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        torch.cuda.profiler.stop()\n\n    def step(self) -> None:\n        pass\n\n\nclass PyTorchProfiler:\n    \"\"\"Profiler which relies on native Pytorch profiling. Current setting of the profiler\n    captures traces, memory footprint and other info that could be read via TensorBoard.\n    \"\"\"\n\n    ACTIVITIES = [\n        torch.profiler.ProfilerActivity.CPU,\n        torch.profiler.ProfilerActivity.CUDA,\n    ]\n\n    def __init__(self, main_profiler: \"_Profiler\") -> None:\n        self.main_profiler = main_profiler\n        self.num_steps = 0\n        self.pytorch_profiler = torch.profiler.profile(\n            on_trace_ready=self._on_trace,\n            profile_memory=True,\n            record_shapes=True,\n            with_stack=True,\n            with_flops=True,\n            activities=self.ACTIVITIES,\n        )\n\n    def _on_trace(self, prof: torch.profiler.profiler.profile) -> None:\n        activities_str = \"_\".join(a.name for a in self.ACTIVITIES)\n        dir_name = str(\n            self.main_profiler.output_dir\n            / f\"profile_{activities_str}_{self.main_profiler.done_steps:06}\"\n        )\n        worker_name = self.main_profiler.worker_name\n        if worker_name == \"\":\n            worker_name = f\"{socket.gethostname()}_{os.getpid()}\"\n            if dist.is_available() and dist.is_initialized():\n                # Left-pad rank with zeros to make them all of the same length\n                rank = f\"{dist.get_rank()}\".zfill(len(f\"{dist.get_world_size() - 1}\"))\n                worker_name = f\"rank{rank}_{worker_name}\"\n        os.makedirs(dir_name, exist_ok=True)\n        file_name = f\"{worker_name}.{time.time_ns()}.pt.trace.json.gz\"\n        prof.export_chrome_trace(os.path.join(dir_name, file_name))\n        csv_file_name = f\"kernels_{worker_name}.{time.time_ns()}.csv\"\n        self._preprocess_trace(prof, os.path.join(dir_name, csv_file_name))\n        try:\n            self._analyze_trace(prof)\n        except Exception as exc:\n            self.main_profiler.summary.append((\"TraceAnalysis\", \"Error\"))\n            logger.warning(\"Exception analyzing kineto trace\", exc_info=exc)\n\n    def _preprocess_trace(\n        self, prof: torch.profiler.profiler.profile, file_name: str\n    ) -> None:\n        if prof.profiler is None or prof.profiler.kineto_results is None:\n            return\n        with open(file_name, \"w\", newline=\"\") as file:\n            writer = csv.writer(file)\n            for e in prof.profiler.kineto_results.events():\n                if (\n                    e.device_type().name == \"CUDA\"\n                    and not e.is_user_annotation()\n                    and e.duration_ns() > 0\n                ):\n                    writer.writerow([e.name(), f\"{e.duration_ns() / 1_000}\"])\n\n    def _analyze_trace(self, prof: torch.profiler.profiler.profile) -> None:\n        if prof.profiler is None or prof.profiler.kineto_results is None:\n            return\n        results = AnalyzedTrace.from_profile(prof.profiler.kineto_results.events())\n        limits = get_device_limits(torch.device(\"cuda\"))\n        hw_flops: Dict[torch.dtype, float] = {}\n        if limits is not None:\n            for dtype, tflops in limits.gemm_tflops.items():\n                hw_flops[dtype] = tflops * (1000**4)\n        total_hfu = results.compute_hfu(hw_flops)\n        total_mfu = results.compute_mfu(hw_flops)\n        total_flop = sum(\n            results.compute_num_ops(dtype)\n            for dtype in results.operations_per_dtype_fw.keys()\n        )\n        s = self.main_profiler.summary\n        s.append(\n            (\"Step time (ms)\", f\"{int(results.total_time_s * 1000 / self.num_steps)}\")\n        )\n        s.append((\"TFlop/step\", f\"{total_flop / (self.num_steps * 1000**4):0.1f}\"))\n        s.append((\"TFlops\", f\"{total_flop / (results.total_time_s * 1000**4):0.1f}\"))\n        s.append((\"HFU\", f\"{total_hfu:0.3f}\"))\n        s.append((\"MFU\", f\"{total_mfu:0.3f}\"))\n\n    def __enter__(self):\n        torch.cuda.synchronize()\n        self.pytorch_profiler.__enter__()\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        torch.cuda.synchronize()\n        self.pytorch_profiler.__exit__(exc_type, exc_val, exc_tb)\n\n    def step(self) -> None:\n        self.pytorch_profiler.step()\n        self.num_steps += 1\n\n\nclass PyTorchProfiler_CUDAOnly(PyTorchProfiler):\n    # This profiler does not profile the CPU-side of things\n    # so we expect it to have almost no overhead\n    ACTIVITIES = [torch.profiler.ProfilerActivity.CUDA]\n\n    def _analyze_trace(self, prof: torch.profiler.profiler.profile) -> None:\n        # Can't analyze trace without CPU trace for operator shapes etc...\n        pass\n\n\nclass MemSnapshotsProfiler:\n    \"\"\"Profiler that captures memory traces for allocation and deallocation of memory for\n    tensors.\n    \"\"\"\n\n    def __init__(self, main_profiler: \"_Profiler\") -> None:\n        self.main_profiler = main_profiler\n        self.enabled = False\n\n    @property\n    def _has_trace_plot(self) -> bool:\n        return hasattr(torch.cuda._memory_viz, \"trace_plot\")\n\n    def __enter__(self):\n        if not self._has_trace_plot:\n            return\n        self.enabled = True\n        # TODO: This does not show the previous memory allocations\n        # We could at least have a placeholder with how much\n        # memory was allocated before\n        torch.cuda.memory._record_memory_history(\n            True,\n            # keep 100,000 alloc/free events from before the snapshot\n            trace_alloc_max_entries=100000,\n            # record stack information for the trace events\n            trace_alloc_record_context=True,\n        )\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        if not self._has_trace_plot:\n            self.main_profiler.summary.append(\n                (\"MemTrace\", \"(not available with your Pytorch version)\")\n            )\n            return\n        assert self.enabled\n        snapshot = torch.cuda.memory._snapshot()\n        torch.cuda.memory._record_memory_history(False)\n        # No data was recorded - avoids a `ValueError` in `trace_plot`\n        if all(len(t) == 0 for t in snapshot[\"device_traces\"]):\n            self.main_profiler.summary.append((\"MemTrace\", \"(no allocation recorded)\"))\n            return\n        # Dump to disk\n        filename = self.main_profiler._create_output_filename(\"memory_trace_plot.html\")\n        self.main_profiler.summary.append((\"MemTrace\", filename))\n        with open(filename, \"w+\") as fd:\n            fd.write(\n                torch.cuda._memory_viz.trace_plot(\n                    snapshot, device=None, plot_segments=False\n                )\n            )\n\n    def step(self) -> None:\n        pass\n\n\n@dataclass\nclass _ProfilerState:\n    cls: Any\n    iter_begin: int\n    iter_end: int\n    object: Any = None\n\n\nclass _Profiler:\n    _CURRENT_PROFILER = None\n\n    def __init__(\n        self,\n        output_dir: str,\n        schedule: Sequence[Tuple[Any, int, int]],\n        module: Optional[nn.Module],\n    ) -> None:\n        self.check_schedule(schedule)\n        self.schedule = schedule\n        self.done_steps = 0\n        self.output_dir = Path(output_dir).absolute()\n        self.output_dir.mkdir(exist_ok=True, parents=True)\n        self.worker_name = \"\"\n        if dist.is_available() and dist.is_initialized():\n            # Left-pad rank with zeros to make them all of the same length\n            rank = f\"{dist.get_rank()}\".zfill(len(f\"{dist.get_world_size() - 1}\"))\n            self.worker_name = f\"rank{rank}_{socket.gethostname()}_{os.getpid()}\"\n\n        self.module = weakref.ref(module if module is not None else nn.Module())\n        self.init_schedule()\n\n    def init_schedule(self, offset: int = 0) -> None:\n        self.profilers: List[_ProfilerState] = sorted(\n            [\n                _ProfilerState(cls, begin + offset, end + offset)\n                for cls, begin, end in self.schedule\n            ],\n            key=lambda x: x.iter_begin,\n        )\n        self.last_step = self.profilers[-1].iter_end if self.profilers else 0\n        self.summary: List[Tuple[str, str]] = []\n\n    def check_schedule(self, schedule: Sequence[Tuple[Any, int, int]]) -> None:\n        if len(schedule) == 0:\n            logger.warning(\n                \"You specified empty schedule for profiling. No data will be captured.\"\n            )\n\n        pq: Any = queue.PriorityQueue()\n        for cls, begin, end in schedule:\n            assert (\n                begin >= 0\n            ), f\"Begin step of profiler must be non-negative, found: {begin}\"\n            assert end > 0, f\"End step of profiler must be positive, found: {end}\"\n            assert (\n                begin < end\n            ), f\"Start must be before the end, found: begin={begin} and end={end}\"\n\n            pq.put((begin, end))\n\n        prev_end = -1\n        for begin, end in pq.queue:\n            assert begin >= prev_end, (\n                \"There is some overlapping in profiler scheduling. Please do not\"\n                + \" overlap profilers by step as they may affect each other. Schedule:\"\n                + f\" {schedule}\"\n            )\n            prev_end = end\n\n    def update_profilers_on_step(self) -> None:\n        for p in self.profilers:\n            if p.iter_begin <= self.done_steps and self.done_steps < p.iter_end:\n                if p.object is None:\n                    o = p.cls(self)\n                    logging.info(f\"Starting {p.cls.__name__} profiler...\")\n                    o.__enter__()\n                    p.object = o\n                else:\n                    p.object.step()\n            else:\n                if p.object is not None:\n                    o = p.object\n                    p.object = None\n                    logging.info(f\"Shutting down {p.cls.__name__} profiler...\")\n                    # Make sure the profiler's `step` function is called\n                    # $N times when we do $N steps with this profiler.\n                    o.step()\n                    o.__exit__(None, None, None)\n\n    def _create_output_filename(self, filename: str) -> Path:\n        \"\"\"\n        Returns where to write a file with desired filename.\n        Handles the case where we are in distributed settings, or when\n        we need to output the same file multiple times (eg if a profiler\n        runs for several steps)\n        \"\"\"\n        if self.worker_name != \"\":\n            file = Path(filename)\n            folder = self.output_dir / file.stem\n            folder.mkdir(parents=True, exist_ok=True)\n            return folder / f\"{self.done_steps:06}_{self.worker_name}{file.suffix}\"\n        return self.output_dir / f\"{self.done_steps:06}_{filename}\"\n\n    def start(self):\n        self.__enter__()\n\n    def stop(self, exc_type=None, exc_val=None, exc_tb=None):\n        self.__exit__(exc_type, exc_val, exc_tb)\n\n    def __enter__(self):\n        if _Profiler._CURRENT_PROFILER is not None:\n            raise ValueError(\"Only one xformers profiler can be active at a time\")\n        _Profiler._CURRENT_PROFILER = self\n        self.update_profilers_on_step()\n\n        return self\n\n    def __exit__(self, exc_type, exc_val, exc_tb):\n        _Profiler._CURRENT_PROFILER = None\n\n        for p in self.profilers:\n            if p.object is not None:\n                p.object.__exit__(exc_type, exc_val, exc_tb)\n\n    def step(self) -> None:\n        \"\"\"Signals the profiler that the next profiling step has started.\"\"\"\n        self.done_steps += 1\n\n        if self.done_steps <= self.last_step:\n            self.update_profilers_on_step()\n        if self.done_steps == self.last_step:\n            logger.info(\"xFormers profiler done. %s\", self.format_summary())\n\n        # Check if we triggered a manual profile step\n        CHECK_TRIGGER_EVERY = 10\n        if (\n            self.done_steps > self.last_step\n            and (self.done_steps % CHECK_TRIGGER_EVERY) == 0\n        ):\n            try:\n                (self.output_dir / \"trigger\").unlink()\n                (\n                    self.output_dir\n                    / f\"trigger.{self.done_steps + CHECK_TRIGGER_EVERY:09}\"\n                ).write_text(self.worker_name)\n            except FileNotFoundError:\n                pass\n            step_trigger = self.output_dir / f\"trigger.{self.done_steps:09}\"\n            if step_trigger.exists():\n                logger.info(\n                    \"xFormers profiler manually triggered at step %d\", self.done_steps\n                )\n                self.init_schedule(offset=self.done_steps + 1)\n\n    def format_summary(self) -> str:\n        if len(self.summary) == 0:\n            return \"\"\n        pad_titles = max(len(title) for title, value in self.summary)\n        return \"summary:\\n\" + \"\\n\".join(\n            [f\"  {title.ljust(pad_titles)}: {value}\" for title, value in self.summary]\n        )\n"
  },
  {
    "path": "xformers/profiler/profiler_dcgm.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport sys\n\nfrom .profiler import _Profiler, logger\n\nDCGM_PROFILER_AVAILABLE = False\ntry:\n    DCGM_PYTHON_PATH: str = \"/usr/local/dcgm/bindings/python3\"\n    sys.path.insert(0, DCGM_PYTHON_PATH)\n    from .profiler_dcgm_impl import DCGMProfiler\n\n    DCGM_PROFILER_AVAILABLE = True\nexcept ModuleNotFoundError:\n\n    class DCGMProfiler:  # type: ignore\n        \"\"\"The dummy DCGM Profiler.\"\"\"\n\n        def __init__(\n            self,\n            main_profiler: \"_Profiler\",\n            gpus_to_profile=None,\n            field_ids_to_profile=None,\n            updateFreq=None,\n        ) -> None:\n            pass\n\n        def __enter__(self) -> None:\n            logger.warning(\n                f\"Unable to find python bindings at {DCGM_PYTHON_PATH}. \"\n                \"No data will be captured.\"\n            )\n\n        def __exit__(self, exc_type, exc_val, exc_tb) -> None:\n            pass\n\n        def step(self) -> None:\n            pass\n\n\ndel sys.path[0]\n"
  },
  {
    "path": "xformers/profiler/profiler_dcgm_impl.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nfrom typing import List, Optional, Set, Tuple, Union\n\nimport dcgm_fields\nimport torch\nfrom dcgm_fields import DcgmFieldGetById\nfrom dcgm_structs import DCGM_GROUP_EMPTY, DCGM_OPERATION_MODE_AUTO\nfrom pydcgm import DcgmFieldGroup, DcgmGroup, DcgmHandle\n\nfrom .profiler import _Profiler, logger\n\n\nclass DCGMProfiler:\n    \"\"\"Profiler that triggers start of DCGM profiler.\"\"\"\n\n    def __init__(\n        self,\n        main_profiler: \"_Profiler\",\n        gpus_to_profile: Optional[Tuple[int, ...]] = None,\n        field_ids_to_profile=(\n            dcgm_fields.DCGM_FI_PROF_SM_ACTIVE,\n            dcgm_fields.DCGM_FI_PROF_SM_OCCUPANCY,\n            dcgm_fields.DCGM_FI_PROF_PIPE_TENSOR_ACTIVE,\n            dcgm_fields.DCGM_FI_PROF_DRAM_ACTIVE,\n            dcgm_fields.DCGM_FI_PROF_PCIE_TX_BYTES,\n            dcgm_fields.DCGM_FI_PROF_PCIE_RX_BYTES,\n            dcgm_fields.DCGM_FI_PROF_NVLINK_TX_BYTES,\n            dcgm_fields.DCGM_FI_PROF_NVLINK_RX_BYTES,\n        ),\n        updateFreq: int = 5000,  # in microseconds\n    ) -> None:\n        \"\"\"\n        Args:\n            main_profiler: The main profiler object.\n            gpus_to_profile: A tuple of integers representing the GPUs to profile. If `None`,\n                then the \"default\" GPU is used.\n            field_ids_to_profile:\n                See https://github.com/NVIDIA/DCGM/blob/master/testing/python3/dcgm_fields.py#L436\n                for a full list of available fields. Note that not all fields are profilable.\n            updateFreq: The interval of two consecutive updates of each field. Defaults to 5000 microseconds.\n                This is a good tradeoff between performance and accuracy.\n                An even smaller updateFreq is not supported well by A100.\n                If the step to profile takes more than 5000 microseconds, then a larger updateFreq could also be used.\n        \"\"\"\n        self.main_profiler = main_profiler\n        self.updateFreq = updateFreq\n\n        self.dcgmHandle = DcgmHandle(\n            ipAddress=\"127.0.0.1\", opMode=DCGM_OPERATION_MODE_AUTO\n        )\n\n        if gpus_to_profile is None:\n            default_gpu: int = torch.empty([], device=\"cuda\").device.index\n            self.dcgmGroup = self.create_dcgm_group((default_gpu,))\n        else:\n            self.dcgmGroup = self.create_dcgm_group(gpus_to_profile)\n\n        self.dcgmFieldGroup = self.create_profiling_field_group(field_ids_to_profile)\n\n    def create_dcgm_group(\n        self, gpus_to_profile: Union[Tuple[int], Tuple[int, ...]]\n    ) -> Optional[DcgmGroup]:\n        if self.dcgmHandle is None:\n            return None\n\n        dcgmSystem = self.dcgmHandle.GetSystem()\n        supportedGPUs = dcgmSystem.discovery.GetAllSupportedGpuIds()\n\n        valid_gpus_to_profile: List[int] = [\n            gpu for gpu in gpus_to_profile if gpu in supportedGPUs\n        ]\n        if len(valid_gpus_to_profile) < 1:\n            logger.warning(\n                f\"The provided GPUs are not supported on this system: \"\n                f\"provided {gpus_to_profile}, supported {supportedGPUs}. \"\n                f\"No data will be captured.\"\n            )\n            return None\n\n        dcgmGroup = DcgmGroup(\n            self.dcgmHandle,\n            groupName=\"DCGMProfiler\",\n            groupType=DCGM_GROUP_EMPTY,\n        )\n\n        for gpu in valid_gpus_to_profile:\n            dcgmGroup.AddGpu(gpu)\n\n        return dcgmGroup\n\n    def get_profilable_fields(self) -> Set[int]:\n        assert self.dcgmGroup is not None\n\n        dcgmMetricGroups = self.dcgmGroup.profiling.GetSupportedMetricGroups()\n        profilableFieldIds = set()\n        for group_idx in range(dcgmMetricGroups.numMetricGroups):\n            metric_group = dcgmMetricGroups.metricGroups[group_idx]\n            for field_id in metric_group.fieldIds[: metric_group.numFieldIds]:\n                profilableFieldIds.add(field_id)\n        return profilableFieldIds\n\n    def create_profiling_field_group(\n        self,\n        fieldIdsToProfile: Optional[Tuple[int, ...]],\n    ) -> Optional[DcgmFieldGroup]:\n        if self.dcgmGroup is None:\n            return None\n\n        # Get all field ids that can be profiled.\n        profilableFieldIds = self.get_profilable_fields()\n\n        # Check which of the provided field ids are valid and invalid.\n        if fieldIdsToProfile is None:\n            validFieldIds = list(profilableFieldIds)\n            invalidFieldIds = []\n        else:\n            validFieldIds = [\n                field_id\n                for field_id in fieldIdsToProfile\n                if field_id in profilableFieldIds\n            ]\n            invalidFieldIds = [\n                field_id\n                for field_id in fieldIdsToProfile\n                if field_id not in profilableFieldIds\n            ]\n\n        if not validFieldIds:\n            logger.warning(\n                \"None of the provided field ids could be profiled.\\n\"\n                f\"  Provided: {fieldIdsToProfile}\\n\"\n                f\"  Supported: {profilableFieldIds}\\n\"\n                \"No data will be captured.\"\n            )\n            return None\n\n        if invalidFieldIds:\n            logger.warning(\n                f\"The following field ids cannot be profiled: {invalidFieldIds}. \"\n                f\"Profiling {validFieldIds} only.\"\n            )\n        dcgmFieldGroup = DcgmFieldGroup(\n            self.dcgmHandle, name=\"Profiling\", fieldIds=validFieldIds\n        )\n        return dcgmFieldGroup\n\n    def __enter__(self) -> None:\n        if self.dcgmGroup is not None and self.dcgmFieldGroup is not None:\n            self.dcgmGroup.samples.WatchFields(\n                self.dcgmFieldGroup, self.updateFreq, 3600, 0\n            )\n\n            # Start collecting the profiling results run in background.\n            self.profiling_results = self.dcgmGroup.samples.GetAllSinceLastCall(\n                None, self.dcgmFieldGroup\n            )\n\n            # It is necessary to call GetAllSinceLastCall and EmptyValues twice\n            # to clear old data from previous profilings\n            # (otherwise the new profiling data is appended to the old data from previous profiling).\n            self.dcgmGroup.samples.GetAllSinceLastCall(\n                self.profiling_results, self.dcgmFieldGroup\n            )\n            self.profiling_results.EmptyValues()\n\n            self.dcgmGroup.samples.GetAllSinceLastCall(\n                self.profiling_results, self.dcgmFieldGroup\n            )\n            self.profiling_results.EmptyValues()\n\n    def __exit__(self, exc_type, exc_val, exc_tb) -> None:\n        if self.dcgmGroup is not None and self.dcgmFieldGroup is not None:\n            self.dcgmGroup.samples.UnwatchFields(self.dcgmFieldGroup)\n\n            # Delete the group.\n            self.dcgmGroup.Delete()\n            del self.dcgmGroup\n            self.dcgmGroup = None\n\n            # Disconnect from the hostengine by deleting the DcgmHandle object.\n            del self.dcgmHandle\n            self.dcgmHandle = None\n\n    def step(self) -> None:\n        if self.dcgmGroup is not None and self.dcgmFieldGroup is not None:\n            # Collect the profiling results.\n            self.dcgmGroup.samples.GetAllSinceLastCall(\n                self.profiling_results, self.dcgmFieldGroup\n            )\n\n            # Save profiling results to log.\n            for gpu_id in self.profiling_results.values.keys():\n                for field_id in self.profiling_results.values[gpu_id].keys():\n                    field_name = DcgmFieldGetById(field_id).tag\n\n                    field_avg_val = 0.0\n                    num_vals = 0\n                    for gpu_field_time in self.profiling_results.values[gpu_id][\n                        field_id\n                    ]:\n                        if gpu_field_time.value is not None:\n                            field_avg_val = (\n                                field_avg_val * num_vals + gpu_field_time.value\n                            ) / (num_vals + 1)\n                            num_vals += 1\n                    self.main_profiler.summary.append(\n                        (f\"GPU {gpu_id}, {field_name}({field_id})\", f\"{field_avg_val}\")\n                    )\n\n            # Clear the profiling results to get ready for the next collection.\n            self.profiling_results.EmptyValues()\n"
  },
  {
    "path": "xformers/sparse/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nfrom .blocksparse_tensor import BlockSparseTensor  # noqa: F401\n"
  },
  {
    "path": "xformers/sparse/blocksparse_tensor.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport logging\n\nimport torch\n\nfrom xformers.ops import masked_matmul\n\nlogger = logging.getLogger(\"xformers\")\n\n\n# TODO: This is all now deprecated because PyTorch has its own blocksparse ops\ndef _spmm(b, layout, values):\n    N, nnz, _, block_size = values.shape\n    br = b.reshape(\n        b.shape[0], b.shape[1], b.shape[2] // block_size, block_size, b.shape[3]\n    )\n    # perform matmul on blocks\n    h, r, c = layout.nonzero(as_tuple=True)\n    temp = values @ br[:, h, c, :]\n\n    linear_idx = h * (b.shape[2] // block_size) + r\n    out = torch.zeros(\n        N,\n        b.shape[1] * layout.shape[-2],\n        block_size,\n        b.shape[3],\n        dtype=b.dtype,\n        device=b.device,\n    )\n    # now aggregate the results of the different blocks\n    out.index_add_(1, linear_idx.to(b.device), temp)\n    out = out.reshape(N, b.shape[1], -1, b.shape[3])\n    return out\n\n\ndef _softmax(layout, values):\n    h, r, c = layout.nonzero(as_tuple=True)\n    norms = torch.logsumexp(values, dim=-1, keepdim=True)\n    linear_idx = h * layout.shape[1] + r\n\n    out_t = torch.zeros(\n        norms.shape[0],\n        layout.shape[0] * layout.shape[1],\n        norms.shape[2],\n        norms.shape[3],\n        dtype=norms.dtype,\n        device=norms.device,\n    )\n    max_val = norms.max()\n    out_t.index_add_(\n        1, linear_idx.to(values.device), (norms - max_val).exp()\n    ).clamp_min_(1e-24).log_().add_(max_val)\n    out = torch.exp(values - out_t[:, linear_idx])\n    return out\n\n\ndef _sddmm(a, b, layout):\n    block_size = a.shape[-2] // layout.shape[-2]\n    a = a.reshape(\n        a.shape[0], a.shape[1], a.shape[2] // block_size, block_size, a.shape[3]\n    )\n    b = b.reshape(\n        b.shape[0], b.shape[1], b.shape[2] // block_size, block_size, b.shape[3]\n    )\n\n    h, r, c = layout.nonzero(as_tuple=True)\n\n    out = torch.einsum(\"nhik,nhjk->nhij\", a[:, h, r, :, :], b[:, h, c, :, :])\n    return out\n\n\nclass BlockSparseTensor(torch.Tensor):\n    @staticmethod\n    def __new__(cls, values, layout):\n        kwargs = {}\n        kwargs[\"device\"] = values.device\n        kwargs[\"dtype\"] = values.dtype\n        kwargs[\"layout\"] = values.layout\n        kwargs[\"requires_grad\"] = values.requires_grad\n        assert values.ndim == 4\n        B, _, block_size, _ = values.shape\n        C, h, w = layout.shape\n        # TODO validate shape of layout vs values\n        shape = (B, C, block_size * h, block_size * w)\n        return torch.Tensor._make_wrapper_subclass(cls, shape, **kwargs)\n\n    def __init__(self, values, layout):\n        assert values.shape[-2] == values.shape[-1]\n        assert (\n            values.device == layout.device\n        ), \"Both values and layout need to reside on the same device\"\n        block_size = values.shape[-1]\n        # TODO: make this check conditioned on the use of Triton\n        assert block_size >= 16, \"Minimum block size is 16, for now at least\"\n\n        # Pure blocksparse data\n        self.__values = values\n        self.__layout = layout\n\n    def __repr__(self):\n        return f\"block_sparse_tensor(shape={self.shape}, values={self.__values})\"\n\n    def values(self):\n        return self.__values\n\n    @classmethod\n    def _raw_wrap(cls, values, layout):\n        matrix = cls.__new__(cls, values, layout)\n        matrix.__values = values\n        matrix.__layout = layout\n        return matrix\n\n    @classmethod\n    def _wrap(cls, values, bmat):\n        matrix = cls.__new__(cls, values, bmat.__layout)\n        matrix.__values = values\n        matrix.__layout = bmat.__layout\n        return matrix\n\n    @classmethod\n    def _bmm(cls, arg0, arg1):\n        if not (isinstance(arg0, cls) and type(arg1) is torch.Tensor):\n            return NotImplemented\n        res = _spmm(arg1, arg0.__layout, arg0.__values)\n        return res\n\n    @classmethod\n    def _masked_matmul(cls, a, b, mask):\n        if not (type(a) is torch.Tensor and type(b) is torch.Tensor):\n            return NotImplemented\n        b = b.transpose(-2, -1)\n        assert b.is_contiguous()\n        res = _sddmm(a, b, mask.__layout)\n        return cls._wrap(res, mask)\n\n    @classmethod\n    def _softmax(cls, arg0, dim):\n        if not (dim == -1 or dim == 2):\n            return NotImplemented\n        res = _softmax(arg0.__layout, arg0.__values)\n        return cls._wrap(res, arg0)\n\n    @classmethod\n    def _to(cls, arg0, device):\n        if isinstance(device, str):\n            device = torch.device(device)\n        assert isinstance(device, torch.device)\n        return cls(\n            arg0.__values.to(device=device),\n            arg0.__layout,\n        )\n\n    @classmethod\n    def _copy(cls, arg0, arg1):\n        if not (isinstance(arg0, cls) and isinstance(arg1, cls)):\n            return NotImplemented\n        assert arg0.shape == arg1.shape\n        av0, av1 = arg0.__values, arg1.__values\n        av0.resize_as_(av1).copy_(av1)\n        av0, av1 = arg0.__layout, arg1.__layout\n        av0.resize_as_(av1).copy_(av1)\n        return arg0\n\n    @classmethod\n    def _equal(cls, arg0, arg1):\n        if not (isinstance(arg0, cls) and isinstance(arg1, cls)):\n            return NotImplemented\n        if arg0.shape != arg1.shape:\n            return False\n        if not torch.equal(arg0.__values, arg1.__values):\n            return False\n        if not torch.equal(arg0.__layout, arg1.__layout):\n            return False\n        return True\n\n    @classmethod\n    def _to_dense(cls, arg0):\n        # out = torch.zeros(arg0.shape, dtype=arg0.dtype, device=arg0.device, requires_grad=arg0.requires_grad)\n        out = torch.zeros(arg0.shape, dtype=arg0.dtype, device=arg0.device)\n        values = arg0.__values\n        layout = arg0.__layout\n        block_size = values.shape[-1]\n        blocks_i = layout.shape[-2]\n        blocks_j = layout.shape[-1]\n\n        out_r = out.reshape(\n            arg0.shape[0], arg0.shape[1], blocks_i, block_size, blocks_j, block_size\n        )\n\n        for idx, (h, i, j) in enumerate(zip(*layout.nonzero(as_tuple=True))):\n            out_r[:, h, i, :, j, :] = values[:, idx, :, :]\n\n        return out\n\n    @classmethod\n    def __torch_function__(cls, func, types, args=(), kwargs=None):\n        if kwargs is None:\n            kwargs = {}\n        if func in [\n            torch.Tensor.bmm,\n            torch.bmm,\n            torch.Tensor.__matmul__,\n            torch.matmul,\n            torch.Tensor.matmul,\n        ]:\n            assert len(args) == 2\n            return cls._bmm(args[0], args[1])\n\n        if func in [torch.Tensor.softmax, torch.nn.functional.softmax, torch.softmax]:\n            return cls._softmax(args[0], kwargs[\"dim\"])\n\n        if func == masked_matmul:\n            assert len(args) == 3\n            return cls._masked_matmul(args[0], args[1], args[2])\n\n        if func in [torch.nn.functional.dropout, torch.dropout, torch.dropout_]:\n            x = args[0]\n            values = x.__values.clone()\n            values = func(values, *args[1:], **kwargs)\n            return cls._wrap(values, x)\n\n        if func == torch.Tensor.to:\n            # print(args, kwargs)\n            assert len(args) >= 2\n            return cls._to(args[0], args[1])\n            # return cls._to(args[0], kwargs[\"device\"])\n\n        if func in [torch.Tensor.copy_]:\n            assert len(args) == 2\n            return cls._copy(args[0], args[1])\n\n        if func in [torch.Tensor.equal, torch.equal]:\n            assert len(args) == 2\n            return cls._equal(args[0], args[1])\n\n        if func == torch.Tensor.to_dense:\n            assert len(args) == 1\n            return cls._to_dense(args[0])\n\n        if func == torch.Tensor.detach:\n            x = args[0]\n            values = x.__values.clone()\n            values = func(values, *args[1:], **kwargs)\n            return cls._wrap(values, x)\n\n        if func == torch.Tensor.__deepcopy__:\n            x = args[0]\n            memo = args[1]\n            return cls._raw_wrap(\n                x.__values.__deepcopy__(memo),\n                x.__layout.__deepcopy__(memo),\n            )\n\n        if func in [torch.Tensor.grad.__get__, torch.Tensor._grad.__get__]:\n            assert len(args) == 1\n            assert len(kwargs) == 0\n            x = args[0]\n            return cls._wrap(x.__values.grad, x)\n\n        if func == torch.Tensor.requires_grad_:\n            func(args[0].__values)\n\n        with torch._C.DisableTorchFunction():\n            ret = func(*args, **kwargs)\n            # TODO: check this\n            if func in torch.overrides.get_default_nowrap_functions():\n                return ret\n            return torch._tensor._convert(ret, cls)\n\n        return NotImplemented\n\n    @classmethod\n    def __torch_dispatch__(cls, func, types, args, kwargs):\n        return NotImplemented\n"
  },
  {
    "path": "xformers/sparse/utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport torch\n\n\ndef _coo_to_csr(m, n, row_indices, column_indices):\n    # assumes coalesced coo\n    row_offsets = row_indices.bincount(minlength=m).cumsum(0, dtype=row_indices.dtype)\n    row_offsets = torch.nn.functional.pad(row_offsets, (1, 0))\n    return row_offsets, column_indices\n\n\ndef _csr_to_coo(m, n, row_offsets, column_indices):\n    # convert from compressed rows to uncompressed\n    indices = torch.arange(m, dtype=row_offsets.dtype, device=row_offsets.device)\n    row_sizes = torch.diff(row_offsets)\n    row_coo = torch.repeat_interleave(indices, row_sizes.long())\n    return row_coo, column_indices\n\n\ndef _diffsort(a):\n    return torch.argsort(torch.diff(a), dim=0, descending=True)\n\n\ndef _get_transpose_info(m, n, row_indices, row_offsets, column_indices):\n    # strategy:\n    # - uncompress the rows to have data in COO format\n    # - get permutation for stable sort of the columns to get the rows for the transposed matrix\n    # - compress the new rows and return the permutation to be applied on the values\n\n    # convert from compressed rows to uncompressed\n    row_coo, _ = _csr_to_coo(m, n, row_offsets, column_indices)\n\n    # get the permutation for the stable sort\n    row_offsets_t, perm = column_indices.sort(dim=0, stable=True)\n    column_indices_t = row_coo[perm]\n\n    row_offsets_t, _ = _coo_to_csr(n, m, row_offsets_t, column_indices)\n    row_indices_t = _diffsort(row_offsets_t).int()\n\n    return row_indices_t, row_offsets_t, column_indices_t, perm\n\n\ndef _transpose_with_info(values, _transpose_info):\n    row_indices_t, row_offsets_t, column_indices_t, perm = _transpose_info\n    values_t = values[:, perm]\n    return row_indices_t, values_t, row_offsets_t, column_indices_t\n\n\ndef _transpose(m, n, row_indices, values, row_offsets, column_indices):\n    _transpose_info = _get_transpose_info(\n        m, n, row_indices, row_offsets, column_indices\n    )\n    return _transpose_with_info(values, _transpose_info)\n\n\ndef _nonzero_mask_to_sparse_csr_indices(mask, device):\n    \"\"\"Converts dense 2d matrix to a csr sparse matrix.\"\"\"\n\n    assert len(mask.shape) == 2\n    index_dtype = torch.int32\n\n    # Calculate the offset of each row.\n    row_offsets = mask.sum(dim=-1, dtype=index_dtype).cumsum(dim=-1, dtype=index_dtype)\n    row_offsets = torch.nn.functional.pad(row_offsets, (1, 0))\n\n    # Create the row indices and sort them.\n    row_indices = _diffsort(row_offsets).to(index_dtype)\n\n    # Extract the column indices for the nonzero values.\n    column_indices = torch.where(mask)[1].to(index_dtype).contiguous()\n\n    row_indices = row_indices.to(device)\n    row_offsets = row_offsets.to(device)\n    column_indices = column_indices.to(device)\n    return row_indices, row_offsets, column_indices\n\n\ndef _dense_to_sparse(matrix, device):\n    \"\"\"Converts dense 2d matrix to a csr sparse matrix.\"\"\"\n\n    assert len(matrix.shape) == 2\n    value_dtype = torch.float32\n\n    # Extract the nonzero values.\n    mask = matrix != 0\n    values = matrix[mask].to(dtype=value_dtype, device=device)\n\n    row_indices, row_offsets, column_indices = _nonzero_mask_to_sparse_csr_indices(\n        mask, device\n    )\n    return values, row_indices, row_offsets, column_indices\n\n\ndef _round_nnz(mask, divisible_by=4):\n    nonzero = torch.where(mask)\n    nnz = nonzero[0].shape[0]\n    nonzero = tuple(n[: (nnz - nnz % divisible_by)] for n in nonzero)\n    nm = torch.zeros_like(mask)\n    nm[nonzero] = True\n    return nm\n\n\ndef _dense3d_to_sparse(matrix, device):\n    assert len(matrix.shape) == 3\n    mask = matrix != 0\n    if not torch.all(mask == mask[0]):\n        raise ValueError(\"Expected the same sparsity pattern over the batch dimension\")\n\n    # for now, our kernels assume that we have the number of\n    # nnz to be divisible by 4\n    mask = _round_nnz(mask[0], divisible_by=4)\n    mask = mask[None].expand(matrix.shape)\n\n    values = matrix[mask].reshape(matrix.shape[0], -1).to(device)\n    row_indices, row_offsets, column_indices = _nonzero_mask_to_sparse_csr_indices(\n        mask[0], device\n    )\n    return values, row_indices, row_offsets, column_indices\n"
  },
  {
    "path": "xformers/test.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "xformers/triton/__init__.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n"
  },
  {
    "path": "xformers/triton/importing.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport importlib.util\n\n\ndef libdevice_find(name):\n    \"\"\"\n    rsqrt = libdevice_find(\"rsqrt\")\n\n    is a triton-version-friendly way to say\n\n    from triton.language.extra.libdevice import rsqrt\n    \"\"\"\n    locs = (\n        \"triton.language.extra.libdevice\",\n        \"triton.language.extra.cuda.libdevice\",\n        \"triton.language.math\",\n        \"triton.language.libdevice\",\n    )\n\n    for loc in locs:\n        if spec := importlib.util.find_spec(loc):\n            module = importlib.util.module_from_spec(spec)\n            assert spec.loader is not None\n            spec.loader.exec_module(module)\n            return getattr(module, name)\n\n    raise ImportError(f\"Could not find a library to import {name}\")\n"
  },
  {
    "path": "xformers/triton/vararg_kernel.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\nimport ast\nimport copy\nimport functools\nimport linecache\nimport os\nimport sys\nimport tempfile\nfrom enum import Enum\nfrom typing import Any, Dict, List\n\nimport triton\n\n\nclass _ForLoopUnroller(ast.NodeTransformer):\n    def __init__(self, target, inline_variables, loop_iter):\n        self.loop_iter = loop_iter\n        self.target = target\n        self.inline_variables = inline_variables\n\n    def visit_Name(self, node):\n        if node.id != self.target:\n            return node\n        return ast.Name(str(self.loop_iter))\n\n    def visit_Subscript(self, node):\n        # Pattern-matching `value[slice]`\n        if (\n            isinstance(node.slice, ast.Name)\n            and node.slice.id == self.target\n            and isinstance(node.value, ast.Name)\n            and node.value.id in self.inline_variables\n        ):\n            return ast.Name(f\"{node.value.id}{self.loop_iter}\")\n        return node\n\n\nclass _VisitorVarargKernel(ast.NodeTransformer):\n    def __init__(self, N):\n        self.inline_variables = set()\n        self.N = N\n\n    def visit_AnnAssign(self, node):\n        # Pattern-matching:\n        # var_name: \"VAR_ARGS_ARRAY\"\n        if (\n            node.value is None\n            and node.simple == 1\n            and isinstance(node.target, ast.Name)\n            and isinstance(node.annotation, ast.Constant)\n            and node.annotation.value == \"VAR_ARGS_ARRAY\"\n        ):\n            self.inline_variables.add(node.target.id)\n            return []\n        if node.value is not None:\n            node.value = self.visit(node.value)\n        if node.annotation is not None:\n            node.annotation = self.visit(node.annotation)\n        if node.target is not None:\n            node.target = self.visit(node.target)\n        return node\n\n    def visit_arguments(self, node):\n        # Replace `args` annotated with `VAR_ARGS_ARRAY`\n        new_args = []\n        for arg in node.args:\n            if (\n                arg.annotation is not None\n                and isinstance(arg.annotation, ast.Constant)\n                and arg.annotation.value == \"VAR_ARGS_ARRAY\"\n            ):\n                self.inline_variables.add(arg.arg)\n                new_args += [ast.arg(f\"{arg.arg}{i}\") for i in range(self.N)]\n                continue\n            new_args.append(arg)\n        if node.vararg is not None:\n            self.inline_variables.add(node.vararg.arg)\n            new_args += [ast.arg(f\"{node.vararg.arg}{i}\") for i in range(self.N)]\n            node.vararg = None\n            new_args += node.kwonlyargs\n            node.kwonlyargs = []\n        node.args = new_args\n        return node\n\n\nclass _VisitorUnrollKernel(_VisitorVarargKernel):\n    def visit_For(self, node):\n        if (\n            not isinstance(node.iter, ast.Call)\n            or node.iter.func.id != \"range\"\n            or len(node.iter.args) != 1\n            or not isinstance(node.iter.args[0], ast.Call)\n            or node.iter.args[0].func.id != \"len\"\n            or len(node.iter.args[0].args) != 1\n            or node.iter.args[0].args[0].id not in self.inline_variables\n        ):\n            node.body = [self.visit(x) for x in node.body]\n            return node\n        # We know we have to modify this loop\n        new_nodes = []\n        for i in range(self.N):\n            unroller = _ForLoopUnroller(\n                target=node.target.id,\n                inline_variables=self.inline_variables,\n                loop_iter=i,\n            )\n            for body in node.body:\n                body = copy.deepcopy(body)\n                new_node = ast.fix_missing_locations(unroller.visit(body))\n                new_node = self.visit(new_node)\n                new_nodes.append(new_node)\n        return new_nodes\n\n\nclass _VisitorConditionalKernel(_VisitorVarargKernel):\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self.extra_nodes = None\n\n    def visit_Subscript(self, node):\n        if isinstance(node.value, ast.Subscript):\n            node.value = self.visit_Subscript(node.value)\n            return node\n        if not isinstance(node.value, ast.Name):\n            return node\n        if node.value.id in self.inline_variables and isinstance(node.slice, ast.Name):\n            # given `a[i]`, replace with `res`, where `res` is:\n            # a0 if i == 0 else a1 if i== 1 else a2 if i == 2 ...\n            if_statements = [None] * self.N\n            if_statements[-1] = ast.Name(f\"{node.value.id}{self.N - 1}\")\n\n            for i in reversed(range(self.N - 1)):\n                test = ast.Compare(node.slice, [ast.Eq()], [ast.Constant(i)])\n                body = ast.Name(f\"{node.value.id}{i}\")\n                if_statements[i] = ast.IfExp(\n                    test=test,\n                    body=body,\n                    orelse=if_statements[i + 1],\n                )\n\n            return if_statements[0]\n        return node\n\n    def visit_Call(self, node):\n        if (\n            isinstance(node.func, ast.Name)\n            and node.func.id == \"len\"\n            and len(node.args) == 1\n            and isinstance(node.args[0], ast.Name)\n            and node.args[0].id in self.inline_variables\n        ):\n            return ast.Constant(self.N)\n        self.generic_visit(node)\n        return node\n\n\n# Hackfix to get access to get source-code for\n# `exec`-created functions - see https://stackoverflow.com/a/69668999\n_getlines_orig = None\n_FILENAME_TO_SRC: Dict[str, List[str]] = {}\n\n# Materializing the codegen to disk can be useful for external tools, e.g. ncu\n# Disabled by default because writing to disk at module import time is unexpected and error-prone.\n_should_materialize_codegen = os.environ.get(\"XFORMERS_MATERIALIZE_CODEGEN\") == \"1\"\n_should_keep_materialized_source = os.environ.get(\"XFORMERS_KEEP_CODEGEN\") == \"1\"\n_tmp_dir = None\n\n\ndef _monkey_patched_getlines(filename, module_globals=None):\n    if filename in _FILENAME_TO_SRC:\n        return _FILENAME_TO_SRC[filename]\n    else:\n        return _getlines_orig(filename, module_globals)  # type: ignore\n\n\nclass VarargMode(Enum):\n    UNROLL = \"unroll\"\n    CONDITIONAL = \"conditional\"\n\n\n@functools.lru_cache(None)\ndef unroll_varargs(kernel, N: int, mode: VarargMode = VarargMode.UNROLL):\n    \"\"\"\n    Specializes a triton kernel with variable number of inputs\n    to a specific number of inputs `N`.\n\n    `mode` can either be `UNROLL` or `CONDITIONAL`. Both options\n    implement the same functionality, but have different implementations\n    and can have different performance. In `UNROLL` mode, any loops that\n    loop over the varargs will be unrolled. In `CONDITIONAL` mode,\n    indexing into the list of varargs is replaced with conditional\n    statements like `a0 if i==0 else a1 if i==1 else a2...`.\n    `CONDITIONAL` mode is generally better if `N` is large, because it\n    generates a smaller triton kernel that should fit in the\n    instruction cache and will compile faster.\n\n    NOTE: Because it's quite costly to call `triton.jit`,\n    we cache the returned value with `lru_cache`\n    \"\"\"\n    global _FILENAME_TO_SRC, _getlines_orig, _tmp_dir\n\n    k = triton.JITFunction(kernel.fn)\n    parsed = ast.parse(k.src)\n    if mode == VarargMode.UNROLL:\n        nodeVisitor: _VisitorVarargKernel = _VisitorUnrollKernel(N=N)\n    elif mode == VarargMode.CONDITIONAL:\n        nodeVisitor = _VisitorConditionalKernel(N=N)\n    parsed = nodeVisitor.visit(parsed)\n    parsed = ast.fix_missing_locations(parsed)\n\n    # NOTE: `ast.unparse` requires python 3.9+\n    if (sys.version_info.major, sys.version_info.minor) <= (3, 8):\n        raise RuntimeError(\"Error: This functionality requires python 3.9 or above\")\n    new_src = ast.unparse(parsed)  # type: ignore\n\n    # Now we want to `eval` the function, but we need all this\n    # boilerplate code to make sure triton can run `inspect.getsource`\n\n    fn_basename = f\"unroll_varargs-{kernel.fn.__name__}-{mode.value}-{N}\"\n    if _should_materialize_codegen:\n        if not _tmp_dir:\n            _tmp_dir = tempfile.TemporaryDirectory()\n        fn_filename = os.path.join(_tmp_dir.name, f\"{fn_basename}.py\")\n        if _should_keep_materialized_source:\n            # destroy the TemporaryDirectory object\n            _tmp_dir = None\n            # create path if not exists\n            os.makedirs(os.path.dirname(fn_filename), exist_ok=True)\n        with open(fn_filename, \"w\") as f:\n            f.write(new_src)\n    else:\n        # Patch `getlines` only the first time\n        if not _FILENAME_TO_SRC:\n            _getlines_orig = linecache.getlines\n            linecache.getlines = _monkey_patched_getlines\n        fn_filename = f\"<{fn_basename}>\"\n        _FILENAME_TO_SRC[fn_filename] = new_src.splitlines(keepends=True)\n\n    # Create function given source\n    code = compile(new_src, fn_filename, \"exec\")\n\n    _locals: Dict[str, Any] = {}\n    exec(code, kernel.fn.__globals__, _locals)\n    assert len(_locals) == 1, len(_locals)\n    fn = next(iter(_locals.values()))\n\n    jitted_fn = triton.jit(fn)\n    if not hasattr(jitted_fn, \"_unsafe_update_src\"):\n        # Triton older than 3.2\n        jitted_fn.src = new_src\n    return jitted_fn\n\n\n# Note: just import this to make mypy happy\n# when annotating variables with `VAR_ARGS_ARRAY`\nVAR_ARGS_ARRAY = List[Any]\n"
  },
  {
    "path": "xformers/utils.py",
    "content": "# Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.\n#\n# This source code is licensed under the BSD license found in the\n# LICENSE file in the root directory of this source tree.\n\n\nimport importlib\nimport os\nimport sys\nfrom collections import namedtuple\nfrom dataclasses import fields\nfrom typing import Any, Callable, Dict, List, Optional\n\nimport torch\n\nItem = namedtuple(\"Item\", [\"constructor\", \"config\"])\n\n\n# credit: snippet used in ClassyVision (and probably other places)\ndef import_all_modules(root: str, base_module: str) -> List[str]:\n    modules: List[str] = []\n    for file in os.listdir(root):\n        if file.endswith((\".py\", \".pyc\")) and not file.startswith(\"_\"):\n            module = file[: file.find(\".py\")]\n            if module not in sys.modules:\n                module_name = \".\".join([base_module, module])\n                importlib.import_module(module_name)\n                modules.append(module_name)\n\n    return modules\n\n\ndef get_registry_decorator(\n    class_registry, name_registry, reference_class, default_config\n) -> Callable[[str, Any], Callable[[Any], Any]]:\n    def register_item(name: str, config: Any = default_config):\n        \"\"\"Registers a subclass.\n\n        This decorator allows xFormers to instantiate a given subclass\n        from a configuration file, even if the class itself is not part of the\n        xFormers library.\"\"\"\n\n        def register_cls(cls):\n            if name in class_registry:\n                raise ValueError(\"Cannot register duplicate item ({})\".format(name))\n            if not issubclass(cls, reference_class):\n                raise ValueError(\n                    \"Item ({}: {}) must extend the base class: {}\".format(\n                        name, cls.__name__, reference_class.__name__\n                    )\n                )\n            if cls.__name__ in name_registry:\n                raise ValueError(\n                    \"Cannot register item with duplicate class name ({})\".format(\n                        cls.__name__\n                    )\n                )\n\n            class_registry[name] = Item(constructor=cls, config=config)\n            name_registry.add(cls.__name__)\n            return cls\n\n        return register_cls\n\n    return register_item\n\n\ndef generate_matching_config(superset: Dict[str, Any], config_class: Any) -> Any:\n    \"\"\"Given a superset of the inputs and a reference config class,\n    return exactly the needed config\"\"\"\n\n    # Extract the required fields\n    field_names = list(map(lambda x: x.name, fields(config_class)))\n    subset = {k: v for k, v in superset.items() if k in field_names}\n\n    # The missing fields get Noned\n    for k in field_names:\n        if k not in subset.keys():\n            subset[k] = None\n\n    return config_class(**subset)\n\n\n# from https://github.com/openai/triton/blob/95d9b7f4ae21710dc899e1de6a579b2136ea4f3d/python/triton/testing.py#L19\ndef do_bench_cudagraph(\n    fn: Callable, rep: int = 20, grad_to_none: Optional[List[torch.Tensor]] = None\n) -> float:\n    \"\"\"\n    Benchmark the runtime of the provided function.\n    Args:\n        fn: Function to benchmark\n        rep: Repetition time (in ms)\n        grad_to_none: Reset the gradient of the provided tensor to None\n    Returns:\n        Benchmarked runtime in ms\n    \"\"\"\n    if torch.cuda.current_stream() == torch.cuda.default_stream():\n        raise RuntimeError(\n            \"Cannot capture graph in default stream. \"\n            \"Please use side stream in benchmark code.\"\n        )\n    # warmup\n    fn()\n    # step 1 - we estimate the amount of time the kernel call takes\n    # NOTE: this estimate isn't super accurate because the GPU isn't warmed up at this point\n    #       but it is probably good enough\n    if grad_to_none is not None:\n        for x in grad_to_none:\n            x.detach_()\n            x.requires_grad_(True)\n            x.grad = None\n    g = torch.cuda.CUDAGraph()\n    with torch.cuda.graph(g):\n        fn()\n    torch.cuda.synchronize()\n    start_event = torch.cuda.Event(enable_timing=True)\n    end_event = torch.cuda.Event(enable_timing=True)\n    start_event.record()\n    g.replay()\n    end_event.record()\n    torch.cuda.synchronize()\n    estimate_ms = start_event.elapsed_time(end_event)\n    n_repeat = max(1, int(rep / estimate_ms))\n    # step 2 - construct a cuda graph with `n_repeat` unrolled function calls to minimize\n    # host overhead\n    g = torch.cuda.CUDAGraph()\n    with torch.cuda.graph(g):\n        for i in range(n_repeat):\n            if grad_to_none is not None:\n                for x in grad_to_none:\n                    x.grad = None\n            fn()\n    torch.cuda.synchronize()\n    # measure time and return\n    ret = []\n    n_retries = 10\n    for i in range(n_retries):\n        start_event = torch.cuda.Event(enable_timing=True)\n        end_event = torch.cuda.Event(enable_timing=True)\n        start_event.record()\n        g.replay()\n        end_event.record()\n        torch.cuda.synchronize()\n        ret += [start_event.elapsed_time(end_event) / n_repeat]\n    return torch.mean(torch.tensor(ret)).item()\n"
  }
]