mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-03 14:43:48 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 08b519ca77 | |||
| 449cb11938 |
+19
-5
@@ -159,7 +159,21 @@ All four files must be updated together.
|
|||||||
|
|
||||||
## Manifest
|
## Manifest
|
||||||
|
|
||||||
Generate the base manifest after compilation, then namespace the release artifacts as v23:
|
The current test branch uses manifest v25 and requests v25 only. Seed the new
|
||||||
|
manifest from the previous catalog, then replace entries as artifacts are
|
||||||
|
rebuilt with the pinned runtime:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp /path/to/model_names_v24.json /path/to/model_names_v25.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The current tinygrad pin is `f6fc4e3f2c3db5fae1e19cbfbc3ad9fc579a12ae`, from
|
||||||
|
`openpilot` `origin/master` (`bump tg + TC_MIN_GLOBALS`). StarPilot's
|
||||||
|
multi-model `modeld` remains in place; do not replace it with upstream's
|
||||||
|
single-model `modeld`.
|
||||||
|
|
||||||
|
For the older namespace migration workflow, generate the base manifest after
|
||||||
|
compilation and namespace the release artifacts as v23:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python3 scripts/model_rebuild_pipeline.py manifest \
|
python3 scripts/model_rebuild_pipeline.py manifest \
|
||||||
@@ -175,9 +189,9 @@ python3 scripts/namespace_model_artifacts.py \
|
|||||||
|
|
||||||
The namespace command changes IDs such as `tr1422` to `tr14223`, renames the
|
The namespace command changes IDs such as `tr1422` to `tr14223`, renames the
|
||||||
compiled and upload-ready files, and writes an ID map. It preserves display
|
compiled and upload-ready files, and writes an ID map. It preserves display
|
||||||
names and behavioral versions. The current model manager requests v23 only;
|
names and behavioral versions. The current model manager requests v25 only; the
|
||||||
the manifest is fetched from `Models/model_names_v23.json`, while v22 remains
|
manifest is fetched from `Models/model_names_v25.json`. Devices still running
|
||||||
available for devices that have not updated yet.
|
the prior branch continue to request their existing manifest version.
|
||||||
|
|
||||||
After importing newly compiled sources, normalize the release namespace before
|
After importing newly compiled sources, normalize the release namespace before
|
||||||
copying files into either resource repository:
|
copying files into either resource repository:
|
||||||
@@ -205,4 +219,4 @@ Compilation validates JIT capture/replay, pickle round-trip, finite outputs, met
|
|||||||
4. Confirm `driverStateV2` on both supported camera resolutions.
|
4. Confirm `driverStateV2` on both supported camera resolutions.
|
||||||
5. Test download, selection, deletion, randomization, migration, and fallback in both device UIs and Galaxy.
|
5. Test download, selection, deletion, randomization, migration, and fallback in both device UIs and Galaxy.
|
||||||
|
|
||||||
The built-in RDF artifact is `selfdrive/modeld/models/driving_tinygrad.pkl`. If migration cannot download the selected v23 artifact, StarPilot switches to that built-in model.
|
The built-in RDF artifact is `selfdrive/modeld/models/driving_tinygrad.pkl`. If migration cannot download the selected v25 artifact, StarPilot switches to that built-in model.
|
||||||
|
|||||||
@@ -56,14 +56,13 @@ def build_compile_env(*, supercombo: bool = False) -> dict[str, str]:
|
|||||||
existing_pythonpath = env.get("PYTHONPATH", "")
|
existing_pythonpath = env.get("PYTHONPATH", "")
|
||||||
env["PYTHONPATH"] = f"{REPO_ROOT}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else str(REPO_ROOT)
|
env["PYTHONPATH"] = f"{REPO_ROOT}{os.pathsep}{existing_pythonpath}" if existing_pythonpath else str(REPO_ROOT)
|
||||||
defaults = {
|
defaults = {
|
||||||
|
"DEBUG": "0",
|
||||||
"FLOAT16": "1",
|
"FLOAT16": "1",
|
||||||
"IMAGE": "1" if supercombo else "2",
|
"IMAGE": "1" if supercombo else "2",
|
||||||
"JIT_BATCH_SIZE": "0",
|
"JIT_BATCH_SIZE": "0",
|
||||||
"NOLOCALS": "1",
|
"NOLOCALS": "1",
|
||||||
"OPENPILOT_HACKS": "1",
|
"OPENPILOT_HACKS": "1",
|
||||||
} | ({} if supercombo else {
|
}
|
||||||
"DEBUG": "0",
|
|
||||||
})
|
|
||||||
for key, default in defaults.items():
|
for key, default in defaults.items():
|
||||||
try:
|
try:
|
||||||
int(str(env.get(key)), 0)
|
int(str(env.get(key)), 0)
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ OPENPILOT_REPO = "commaai/openpilot"
|
|||||||
RESOURCES_REPO = os.environ.get("STARPILOT_RESOURCES_REPO", "firestar5683/StarPilot-Resources")
|
RESOURCES_REPO = os.environ.get("STARPILOT_RESOURCES_REPO", "firestar5683/StarPilot-Resources")
|
||||||
HF_BUCKET = os.environ.get("STARPILOT_HF_BUCKET", "StarPilot-Driving/StarPilot-Resources")
|
HF_BUCKET = os.environ.get("STARPILOT_HF_BUCKET", "StarPilot-Driving/StarPilot-Resources")
|
||||||
RESOURCE_BRANCH = "Models"
|
RESOURCE_BRANCH = "Models"
|
||||||
MANIFEST_VERSION = "v24"
|
MANIFEST_VERSION = "v25"
|
||||||
DEFAULT_BEHAVIOR_VERSION = "v16"
|
DEFAULT_BEHAVIOR_VERSION = "v16"
|
||||||
DEVICE_ROOT = "/data/openpilot"
|
DEVICE_ROOT = "/data/openpilot"
|
||||||
REPOSITORY_FILE_LIMIT = 100_000_000
|
REPOSITORY_FILE_LIMIT = 100_000_000
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ def test_runtime_scan_excludes_model_weights_but_flags_runtime_code():
|
|||||||
|
|
||||||
|
|
||||||
def test_update_manifest_replaces_one_entry(tmp_path: Path):
|
def test_update_manifest_replaces_one_entry(tmp_path: Path):
|
||||||
manifest = tmp_path / "model_names_v24.json"
|
manifest = tmp_path / "model_names_v25.json"
|
||||||
manifest.write_text(json.dumps({"models": [{"id": "old"}]}) + "\n")
|
manifest.write_text(json.dumps({"models": [{"id": "old"}]}) + "\n")
|
||||||
info = parse_pasted_release(RELEASE_TEXT, "bmrlnapv4", "v16")
|
info = parse_pasted_release(RELEASE_TEXT, "bmrlnapv4", "v16")
|
||||||
path = update_manifest(
|
path = update_manifest(
|
||||||
tmp_path,
|
tmp_path,
|
||||||
info,
|
info,
|
||||||
{"size": 123, "sha256": "a" * 64},
|
{"size": 123, "sha256": "a" * 64},
|
||||||
"v24",
|
"v25",
|
||||||
)
|
)
|
||||||
payload = json.loads(path.read_text())
|
payload = json.loads(path.read_text())
|
||||||
assert len(payload["models"]) == 2
|
assert len(payload["models"]) == 2
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import struct
|
|||||||
from openpilot.system.hardware import HARDWARE, TICI
|
from openpilot.system.hardware import HARDWARE, TICI
|
||||||
os.environ['GMMU'] = '0'
|
os.environ['GMMU'] = '0'
|
||||||
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
|
os.environ['DEV'] = 'QCOM' if TICI else 'LLVM'
|
||||||
|
try:
|
||||||
|
int(os.getenv('DEBUG', '0'), 0)
|
||||||
|
except ValueError:
|
||||||
|
os.environ['DEBUG'] = '0'
|
||||||
from tinygrad.device import Device
|
from tinygrad.device import Device
|
||||||
from tinygrad.tensor import Tensor
|
from tinygrad.tensor import Tensor
|
||||||
import time
|
import time
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1 +1 @@
|
|||||||
a77db33c2e2d6a7570dc2a4a70c2b877429ee8bd9ca5dfeda74b5a41231aaff9 driving_tinygrad.pkl
|
31902b114b7fb8455af694d83333a86a44112b83be662e064ffbd67e8daafe72 driving_tinygrad.pkl
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from openpilot.starpilot.common.starpilot_utilities import delete_file
|
|||||||
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH
|
from openpilot.starpilot.common.starpilot_variables import MODELS_PATH
|
||||||
from openpilot.system.hardware.usb import chestnut_firmware_ready
|
from openpilot.system.hardware.usb import chestnut_firmware_ready
|
||||||
|
|
||||||
MANIFEST_CANDIDATES = ("v24",)
|
MANIFEST_CANDIDATES = ("v25",)
|
||||||
MODEL_NAMESPACE_SUFFIX = "3"
|
MODEL_NAMESPACE_SUFFIX = "3"
|
||||||
DEFAULT_MODEL_KEY = "rdf43"
|
DEFAULT_MODEL_KEY = "rdf43"
|
||||||
LOCAL_MODEL_PREFIX = "local-"
|
LOCAL_MODEL_PREFIX = "local-"
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ from openpilot.starpilot.assets.model_manager import MANIFEST_CANDIDATES, ModelM
|
|||||||
from openpilot.starpilot.common.model_versions import UNIFIED_ARTIFACT_FORMAT
|
from openpilot.starpilot.common.model_versions import UNIFIED_ARTIFACT_FORMAT
|
||||||
|
|
||||||
|
|
||||||
def test_v24_is_the_only_manifest_candidate():
|
def test_v25_is_the_only_manifest_candidate():
|
||||||
assert MANIFEST_CANDIDATES == ("v24",)
|
assert MANIFEST_CANDIDATES == ("v25",)
|
||||||
|
|
||||||
|
|
||||||
def test_v24_manifest_is_loaded_from_models_checkout():
|
def test_v25_manifest_is_loaded_from_models_checkout():
|
||||||
assert ModelManager._manifest_paths("v24") == ("Models/model_names_v24.json",)
|
assert ModelManager._manifest_paths("v25") == ("Models/model_names_v25.json",)
|
||||||
|
|
||||||
|
|
||||||
def test_resource_sources_prefer_huggingface_then_github(monkeypatch):
|
def test_resource_sources_prefer_huggingface_then_github(monkeypatch):
|
||||||
@@ -33,9 +33,9 @@ def test_resource_sources_prefer_huggingface_then_github(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_huggingface_manifest_has_root_and_manifests_fallbacks():
|
def test_huggingface_manifest_has_root_and_manifests_fallbacks():
|
||||||
assert ModelManager._hf_manifest_paths("v24") == (
|
assert ModelManager._hf_manifest_paths("v25") == (
|
||||||
"model_names_v24.json",
|
"model_names_v25.json",
|
||||||
"manifests/model_names_v24.json",
|
"manifests/model_names_v25.json",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+34
-29
@@ -4,7 +4,7 @@ inputs:
|
|||||||
python-version:
|
python-version:
|
||||||
description: 'Python version to use'
|
description: 'Python version to use'
|
||||||
required: false
|
required: false
|
||||||
default: '' # if you don't set a version, the native python version will be used
|
default: '3.14'
|
||||||
key:
|
key:
|
||||||
description: 'Key for the python cache'
|
description: 'Key for the python cache'
|
||||||
required: false
|
required: false
|
||||||
@@ -41,12 +41,12 @@ inputs:
|
|||||||
description: "Install LLVM?"
|
description: "Install LLVM?"
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
tinydreno:
|
qemu:
|
||||||
description: "Install tinydreno"
|
description: "Install qemu?"
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
qemu:
|
ninja:
|
||||||
description: "Install qemu"
|
description: "Install ninja?"
|
||||||
required: false
|
required: false
|
||||||
default: 'false'
|
default: 'false'
|
||||||
runs:
|
runs:
|
||||||
@@ -59,18 +59,18 @@ runs:
|
|||||||
echo "OMP_NUM_THREADS=1" >> "$GITHUB_ENV"
|
echo "OMP_NUM_THREADS=1" >> "$GITHUB_ENV"
|
||||||
# no buffers should be over 300MB in CI
|
# no buffers should be over 300MB in CI
|
||||||
echo "MAX_BUFFER_SIZE=300000000" >> "$GITHUB_ENV"
|
echo "MAX_BUFFER_SIZE=300000000" >> "$GITHUB_ENV"
|
||||||
|
if [[ "$RUNNER_OS" == "Linux" ]]; then
|
||||||
|
echo "VIRTUAL_ENV=/opt/venv/${{ inputs.python-version }}" >> "$GITHUB_ENV"
|
||||||
|
echo "UV_PYTHON_INSTALL_DIR=/opt/python" >> "$GITHUB_ENV"
|
||||||
|
else
|
||||||
|
echo "VIRTUAL_ENV=${{ github.workspace }}/.venv" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Set up uv
|
- name: Set up uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b
|
||||||
with:
|
with:
|
||||||
enable-cache: 'false' # see below for manual caching
|
enable-cache: 'false' # see below for manual caching
|
||||||
|
|
||||||
- name: Set up Python ${{ inputs.python-version }}
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
if: inputs.python-version != ''
|
|
||||||
with:
|
|
||||||
python-version: ${{ inputs.python-version }}
|
|
||||||
|
|
||||||
# **** Caching packages ****
|
# **** Caching packages ****
|
||||||
|
|
||||||
- name: Cache Python packages (PR)
|
- name: Cache Python packages (PR)
|
||||||
@@ -109,15 +109,15 @@ runs:
|
|||||||
if: inputs.deps != ''
|
if: inputs.deps != ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
uv venv .venv
|
uv venv --allow-existing --python ${{ inputs.python-version }} "$VIRTUAL_ENV"
|
||||||
DEPS="${{ inputs.deps }}"
|
DEPS="${{ inputs.deps }}"
|
||||||
uv pip install --python .venv -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
uv pip install --python "$VIRTUAL_ENV" -e ".[${DEPS// /,}]" ${{ inputs.pydeps }} --torch-backend cpu --extra-index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/Triton-Nightly/pypi/simple/
|
||||||
- name: Install dependencies in venv (without extra)
|
- name: Install dependencies in venv (without extra)
|
||||||
if: inputs.deps == ''
|
if: inputs.deps == ''
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
uv venv .venv
|
uv venv --allow-existing --python ${{ inputs.python-version }} "$VIRTUAL_ENV"
|
||||||
uv pip install --python .venv -e . ${{ inputs.pydeps }}
|
uv pip install --python "$VIRTUAL_ENV" -e . ${{ inputs.pydeps }}
|
||||||
- name: Prune uv cache
|
- name: Prune uv cache
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -125,16 +125,15 @@ runs:
|
|||||||
- name: Configure venv
|
- name: Configure venv
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "VIRTUAL_ENV=${{ github.workspace }}/.venv" >> "$GITHUB_ENV"
|
|
||||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||||
echo "${{ github.workspace }}/.venv/Scripts" >> "$GITHUB_PATH"
|
echo "$VIRTUAL_ENV/Scripts" >> "$GITHUB_PATH"
|
||||||
else
|
else
|
||||||
echo "${{ github.workspace }}/.venv/bin" >> "$GITHUB_PATH"
|
echo "$VIRTUAL_ENV/bin" >> "$GITHUB_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# ******************* apt *******************
|
# ******************* apt *******************
|
||||||
- name: Setup apt
|
- name: Setup apt
|
||||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true')
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sudo mkdir -p /var/cache/apt/archives
|
sudo mkdir -p /var/cache/apt/archives
|
||||||
@@ -162,7 +161,7 @@ runs:
|
|||||||
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
echo "deb http://apt.llvm.org/$(lsb_release -cs)/ llvm-toolchain-$(lsb_release -cs)-20 main" | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||||
|
|
||||||
- name: Compute Package List + Hash
|
- name: Compute Package List + Hash
|
||||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true')
|
||||||
id: apt-pkgs
|
id: apt-pkgs
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -187,25 +186,37 @@ runs:
|
|||||||
if [[ "${{ inputs.qemu }}" == "true" ]]; then
|
if [[ "${{ inputs.qemu }}" == "true" ]]; then
|
||||||
pkgs+=" qemu-user-static"
|
pkgs+=" qemu-user-static"
|
||||||
fi
|
fi
|
||||||
|
# **** ninja ****
|
||||||
|
if [[ "${{ inputs.ninja }}" == "true" ]]; then
|
||||||
|
pkgs+=" ninja-build"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "pkgs=$pkgs" >> "$GITHUB_OUTPUT"
|
echo "pkgs=$pkgs" >> "$GITHUB_OUTPUT"
|
||||||
echo "hash=$(echo -n "$pkgs" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
echo "hash=$(echo -n "$pkgs" | sha256sum | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
installed=true
|
||||||
|
for pkg in $pkgs; do
|
||||||
|
info=$(dpkg-query -W -f='${db:Status-Abbrev} ${Version}' "$pkg" 2> /dev/null || true)
|
||||||
|
echo "${pkg}: ${info:-not in dpkg database}"
|
||||||
|
[[ "$info" == ii* ]] || installed=false
|
||||||
|
done
|
||||||
|
echo "installed=$installed" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Cache apt (PR)
|
- name: Cache apt (PR)
|
||||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name == 'pull_request'
|
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && github.event_name == 'pull_request' && steps.apt-pkgs.outputs.installed == 'false'
|
||||||
uses: actions/cache/restore@v5
|
uses: actions/cache/restore@v5
|
||||||
with:
|
with:
|
||||||
path: /var/cache/apt/archives/
|
path: /var/cache/apt/archives/
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||||
- name: Cache apt
|
- name: Cache apt
|
||||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true') && github.event_name != 'pull_request'
|
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && github.event_name != 'pull_request' && steps.apt-pkgs.outputs.installed == 'false'
|
||||||
uses: actions/cache@v5
|
uses: actions/cache@v5
|
||||||
with:
|
with:
|
||||||
path: /var/cache/apt/archives/
|
path: /var/cache/apt/archives/
|
||||||
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
key: ${{ runner.os }}-${{ runner.arch }}-apt-${{ steps.apt-pkgs.outputs.hash }}-${{ env.CACHE_VERSION }}
|
||||||
|
|
||||||
- name: Run apt Update + Install
|
- name: Run apt Update + Install
|
||||||
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true')
|
if: runner.os == 'Linux' && (inputs.opencl == 'true' || inputs.amd == 'true' || inputs.webgpu == 'true' || inputs.llvm == 'true' || inputs.qemu == 'true' || inputs.ninja == 'true') && steps.apt-pkgs.outputs.installed == 'false'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
sudo apt -qq update || true
|
sudo apt -qq update || true
|
||||||
@@ -277,12 +288,6 @@ runs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: brew install llvm@20
|
run: brew install llvm@20
|
||||||
|
|
||||||
# *** tinydreno ***
|
|
||||||
- name: Install tinydreno (linux)
|
|
||||||
if: inputs.tinydreno == 'true' && runner.os == 'Linux'
|
|
||||||
shell: bash
|
|
||||||
run: sudo curl -fL https://github.com/sirhcm/tinydreno/raw/refs/heads/master/libllvm-qcom.so -o /usr/lib/libllvm-qcom.so
|
|
||||||
|
|
||||||
# *** OpenCL ***
|
# *** OpenCL ***
|
||||||
- name: Install rusticl
|
- name: Install rusticl
|
||||||
if: inputs.opencl == 'true'
|
if: inputs.opencl == 'true'
|
||||||
|
|||||||
+4
-43
@@ -35,15 +35,15 @@ jobs:
|
|||||||
key: 'autogen'
|
key: 'autogen'
|
||||||
amd: 'true'
|
amd: 'true'
|
||||||
llvm: 'true'
|
llvm: 'true'
|
||||||
pydeps: 'pyyaml mako'
|
deps: 'autogen'
|
||||||
- name: Install autogen support packages
|
- name: Install autogen support packages
|
||||||
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev libdrm-dev liburing-dev
|
run: sudo apt-get install -y --no-install-recommends libclang-20-dev llvm-20-dev hip-dev libusb-1.0-0-dev libdrm-dev liburing-dev
|
||||||
- name: Regenerate autogen files
|
- name: Regenerate autogen files
|
||||||
run: |
|
run: |
|
||||||
find tinygrad/runtime/autogen -type f -name "*.py" -not -path "*/amd/*" -not -name "__init__.py" -not -name "comgr.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
find tinygrad/runtime/autogen -type f -name "*.py" -not -path "*/amd/*" -not -name "__init__.py" -not -name "metal.py" -not -name "iokit.py" -not -name "corefoundation.py" -not -name "libclang.py" -delete
|
||||||
python3 -c "from tinygrad.runtime.autogen import opencl"
|
python3 -c "from tinygrad.runtime.autogen import opencl"
|
||||||
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv_610, nv"
|
python3 -c "from tinygrad.runtime.autogen import cuda, nvrtc, nvjitlink, nv_570, nv_580, nv_610, nv"
|
||||||
python3 -c "from tinygrad.runtime.autogen import comgr_3, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd, amdgpu_drm"
|
python3 -c "from tinygrad.runtime.autogen import comgr, comgr_3, hsa, hip, amd_gpu, sqtt, rocprof, amdgpu_kd, amdgpu_drm"
|
||||||
python3 -c "from tinygrad.runtime.autogen.am import *"
|
python3 -c "from tinygrad.runtime.autogen.am import *"
|
||||||
python3 -c "from tinygrad.runtime.autogen.nv_regs import *"
|
python3 -c "from tinygrad.runtime.autogen.nv_regs import *"
|
||||||
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, pci, vfio"
|
python3 -c "from tinygrad.runtime.autogen import libc, kfd, io_uring, pci, vfio"
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
python3 -c "from tinygrad.runtime.autogen import mesa"
|
python3 -c "from tinygrad.runtime.autogen import mesa"
|
||||||
python3 -c "from tinygrad.runtime.autogen import avcodec"
|
python3 -c "from tinygrad.runtime.autogen import avcodec"
|
||||||
python3 -c "from tinygrad.runtime.autogen import llvm_qcom"
|
python3 -c "from tinygrad.runtime.autogen import llvm_qcom"
|
||||||
python3 -c "from tinygrad.runtime.autogen import mlx5"
|
python3 -c "from tinygrad.runtime.autogen import mlx5, bnxt"
|
||||||
python3 -c "from tinygrad.runtime.autogen import ggml_common"
|
python3 -c "from tinygrad.runtime.autogen import ggml_common"
|
||||||
REGEN=1 python3 -c "from tinygrad.runtime.autogen import libclang"
|
REGEN=1 python3 -c "from tinygrad.runtime.autogen import libclang"
|
||||||
- name: Check for differences
|
- name: Check for differences
|
||||||
@@ -102,42 +102,3 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: autogen-macos-patch
|
name: autogen-macos-patch
|
||||||
path: autogen-macos.patch
|
path: autogen-macos.patch
|
||||||
|
|
||||||
autogen-comgr-2:
|
|
||||||
name: In-tree Autogen (comgr 2)
|
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Setup Environment
|
|
||||||
uses: ./.github/actions/setup-tinygrad
|
|
||||||
with:
|
|
||||||
key: 'autogen-comgr'
|
|
||||||
- name: Install autogen support packages
|
|
||||||
run: |
|
|
||||||
wget https://repo.radeon.com/rocm/rocm.gpg.key -O - | gpg --dearmor | sudo tee /etc/apt/keyrings/rocm.gpg > /dev/null
|
|
||||||
sudo tee /etc/apt/sources.list.d/rocm.list <<EOF
|
|
||||||
deb [arch=amd64 signed-by=/etc/apt/keyrings/rocm.gpg] https://repo.radeon.com/rocm/apt/6.2 $(lsb_release -cs) main
|
|
||||||
EOF
|
|
||||||
echo -e 'Package: *\nPin: release o=repo.radeon.com\nPin-Priority: 600' | sudo tee /etc/apt/preferences.d/rocm-pin-600
|
|
||||||
sudo apt -qq update || true
|
|
||||||
sudo apt-get install -y --no-install-recommends libclang-20-dev comgr
|
|
||||||
- name: Regenerate autogen files
|
|
||||||
run: |
|
|
||||||
rm tinygrad/runtime/autogen/comgr.py
|
|
||||||
python3 -c "from tinygrad.runtime.autogen import comgr"
|
|
||||||
- name: Check for differences
|
|
||||||
run: |
|
|
||||||
if ! git diff --quiet; then
|
|
||||||
git diff
|
|
||||||
git diff > autogen-comgr2.patch
|
|
||||||
echo "Autogen mismatch detected. Patch available at: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}#artifacts"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
- name: Upload patch artifact
|
|
||||||
if: failure()
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: autogen-comgr2-patch
|
|
||||||
path: autogen-comgr2.patch
|
|
||||||
|
|||||||
+148
-127
@@ -88,13 +88,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['METAL', 'AMD', 'NV']
|
dev: ['METAL', 'AMD', 'NV']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 30
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
env:
|
env:
|
||||||
DEV: ${{ matrix.dev }}
|
DEV: ${{ matrix.dev }}
|
||||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
HCQ2: '0'
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -102,16 +102,11 @@ jobs:
|
|||||||
- name: Setup (AMD)
|
- name: Setup (AMD)
|
||||||
if: ${{ matrix.dev == 'AMD' }}
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py amd rmmod
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py amd kill_pids
|
|
||||||
- name: Setup (NV)
|
- name: Setup (NV)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: sudo lsof -tQ /dev/nvidia* | { xargs -r sudo kill -9 || true; }
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
- name: Symlink models and datasets
|
|
||||||
run: |
|
|
||||||
mkdir -p weights
|
|
||||||
ln -s /raid/weights/LLaMA-3 weights/LLaMA-3
|
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||||
run: |
|
run: |
|
||||||
@@ -121,18 +116,14 @@ jobs:
|
|||||||
run: python3 test/external/process_replay/reset.py
|
run: python3 test/external/process_replay/reset.py
|
||||||
- name: Run llama3.2
|
- name: Run llama3.2
|
||||||
run: BENCHMARK_LOG=llama32_3b-f16 JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m llama3.2:3b-f16 --benchmark --warmup
|
run: BENCHMARK_LOG=llama32_3b-f16 JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m llama3.2:3b-f16 --benchmark --warmup
|
||||||
- name: Run qwen3.6
|
- name: Run qwen3.8
|
||||||
# qwen3.6:35b-a3b doesn't fit on mac
|
# qwen3.8:27b doesn't fit on mac
|
||||||
if: ${{ matrix.dev != 'METAL' }}
|
if: ${{ matrix.dev != 'METAL' }}
|
||||||
run: BENCHMARK_LOG=qwen36_35b-a3b JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m qwen3.6:35b-a3b --benchmark --warmup
|
run: BENCHMARK_LOG=qwen38_27b JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m qwen3.8:27b --benchmark --warmup
|
||||||
- name: Run olmoe
|
- name: Run olmoe
|
||||||
# just metal for now
|
# just metal for now
|
||||||
if: ${{ matrix.dev == 'METAL' }}
|
if: ${{ matrix.dev == 'METAL' }}
|
||||||
run: BENCHMARK_LOG=olmoe JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m olmoe --benchmark --warmup
|
run: BENCHMARK_LOG=olmoe JITBEAM=2 IGNORE_BEAM_CACHE=1 python3 -m tinygrad.llm -m olmoe --benchmark --warmup
|
||||||
- name: Run LLaMA-3 8B on 4 GPUs with BEAM
|
|
||||||
# only run on machines with multiple gpus
|
|
||||||
if: ${{ matrix.dev != 'METAL' }}
|
|
||||||
run: BENCHMARK_LOG=llama3_beam_4gpu JITBEAM=2 IGNORE_BEAM_CACHE=1 CAPTURE_PROCESS_REPLAY=0 python3 examples/llama3.py --size 8B --shard 4 --model weights/LLaMA-3/8B-SF-DPO/ --benchmark --temperature 0
|
|
||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
@@ -143,13 +134,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['METAL', 'AMD', 'NV']
|
dev: ['METAL', 'AMD', 'NV']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
env:
|
env:
|
||||||
DEV: ${{ matrix.dev }}
|
DEV: ${{ matrix.dev }}
|
||||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
HCQ2: '0'
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -157,12 +148,11 @@ jobs:
|
|||||||
- name: Setup (AMD)
|
- name: Setup (AMD)
|
||||||
if: ${{ matrix.dev == 'AMD' }}
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py amd rmmod
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py amd kill_pids
|
|
||||||
- name: Setup (NV)
|
- name: Setup (NV)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: sudo lsof -tQ /dev/nvidia* | { xargs -r sudo kill -9 || true; }
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||||
run: |
|
run: |
|
||||||
@@ -182,10 +172,6 @@ jobs:
|
|||||||
# slow on metal
|
# slow on metal
|
||||||
if: ${{ matrix.dev != 'METAL' }}
|
if: ${{ matrix.dev != 'METAL' }}
|
||||||
run: time BENCHMARK_LOG=cifar DEFAULT_FLOAT=HALF STEPS=1000 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
run: time BENCHMARK_LOG=cifar DEFAULT_FLOAT=HALF STEPS=1000 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
||||||
- name: Run full CIFAR training steps w 6 GPUS
|
|
||||||
# only run on machines with multiple gpus
|
|
||||||
if: ${{ matrix.dev != 'METAL' }}
|
|
||||||
run: time BENCHMARK_LOG=cifar_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
|
||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
@@ -196,13 +182,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['AMD', 'NV']
|
dev: ['AMD', 'NV']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 5
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
env:
|
env:
|
||||||
DEV: ${{ matrix.dev }}
|
DEV: ${{ matrix.dev }}
|
||||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
HCQ2: '0'
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -210,12 +196,11 @@ jobs:
|
|||||||
- name: Setup (AMD)
|
- name: Setup (AMD)
|
||||||
if: ${{ matrix.dev == 'AMD' }}
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py amd rmmod
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py amd kill_pids
|
|
||||||
- name: Setup (NV)
|
- name: Setup (NV)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: sudo lsof -tQ /dev/nvidia* | { xargs -r sudo kill -9 || true; }
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
- name: Symlink models and datasets
|
- name: Symlink models and datasets
|
||||||
run: |
|
run: |
|
||||||
mkdir -p extra/datasets
|
mkdir -p extra/datasets
|
||||||
@@ -227,15 +212,8 @@ jobs:
|
|||||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||||
- name: reset process replay
|
- name: reset process replay
|
||||||
run: test/external/process_replay/reset.py
|
run: test/external/process_replay/reset.py
|
||||||
- name: Run MLPerf resnet eval on training data
|
|
||||||
run: time BENCHMARK_LOG=resnet_eval MODEL=resnet python3 examples/mlperf/model_eval.py
|
|
||||||
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
||||||
run: BENCHMARK_LOG=resnet_10steps DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
run: BENCHMARK_LOG=resnet_10steps DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||||
- name: Run 10 MLPerf ResNet50 training steps (6 gpu)
|
|
||||||
run: BENCHMARK_LOG=resnet_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py
|
|
||||||
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
|
||||||
# TODO: remove BERT_LAYERS once scheduler is fast
|
|
||||||
run: BENCHMARK_LOG=bert_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=72 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
|
|
||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
@@ -246,13 +224,13 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['METAL', 'AMD', 'NV']
|
dev: ['METAL', 'AMD', 'NV']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 15
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
env:
|
env:
|
||||||
DEV: ${{ matrix.dev }}
|
DEV: ${{ matrix.dev }}
|
||||||
HCQ2: ${{ matrix.dev == 'AMD' && '1' || '0' }}
|
HCQ2: '0'
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -260,12 +238,11 @@ jobs:
|
|||||||
- name: Setup (AMD)
|
- name: Setup (AMD)
|
||||||
if: ${{ matrix.dev == 'AMD' }}
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py amd rmmod
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py amd kill_pids
|
|
||||||
- name: Setup (NV)
|
- name: Setup (NV)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: sudo lsof -tQ /dev/nvidia* | { xargs -r sudo kill -9 || true; }
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||||
run: |
|
run: |
|
||||||
@@ -285,6 +262,59 @@ jobs:
|
|||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
|
multigpubenchmark:
|
||||||
|
name: Multi-GPU Benchmarks (DEV=${{ matrix.dev }})
|
||||||
|
runs-on: [self-hosted, "${{ matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
dev: ['AMD', 'NV']
|
||||||
|
timeout-minutes: 20
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -e -o pipefail {0}
|
||||||
|
env:
|
||||||
|
DEV: ${{ matrix.dev }}
|
||||||
|
HCQ2: '0'
|
||||||
|
if: github.repository_owner == 'tinygrad'
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup (AMD)
|
||||||
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
|
run: |
|
||||||
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
|
- name: Setup (NV)
|
||||||
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
|
- name: Symlink models and datasets
|
||||||
|
run: |
|
||||||
|
mkdir -p weights
|
||||||
|
mkdir -p extra/datasets
|
||||||
|
ln -s /raid/weights/LLaMA-3 weights/LLaMA-3
|
||||||
|
ln -s /raid/datasets/imagenet extra/datasets/imagenet
|
||||||
|
- name: setup staging db
|
||||||
|
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||||
|
run: |
|
||||||
|
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
||||||
|
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||||
|
- name: reset process replay
|
||||||
|
run: python3 test/external/process_replay/reset.py
|
||||||
|
- name: Run LLaMA-3 8B on 4 GPUs with BEAM
|
||||||
|
run: BENCHMARK_LOG=llama3_beam_4gpu JITBEAM=2 IGNORE_BEAM_CACHE=1 CAPTURE_PROCESS_REPLAY=0 python3 examples/llama3.py --size 8B --shard 4 --model weights/LLaMA-3/8B-SF-DPO/ --benchmark --temperature 0
|
||||||
|
- name: Run full CIFAR training steps w 6 GPUS
|
||||||
|
run: time BENCHMARK_LOG=cifar_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF STEPS=350 BS=1536 GPUS=6 TARGET_EVAL_ACC_PCT=93.0 python3 examples/hlb_cifar10.py
|
||||||
|
- name: Run MLPerf resnet eval on training data
|
||||||
|
run: time BENCHMARK_LOG=resnet_eval MODEL=resnet python3 examples/mlperf/model_eval.py
|
||||||
|
- name: Run 10 MLPerf ResNet50 training steps (6 gpu)
|
||||||
|
run: BENCHMARK_LOG=resnet_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=1536 GPUS=6 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||||
|
- name: Run 10 MLPerf Bert training steps (6 gpu)
|
||||||
|
# TODO: remove BERT_LAYERS once scheduler is fast
|
||||||
|
run: BENCHMARK_LOG=bert_10steps_6gpu CAPTURE_PROCESS_REPLAY=0 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=72 GPUS=6 BERT_LAYERS=2 MODEL=bert python3 examples/mlperf/model_train.py
|
||||||
|
- name: Run process replay tests
|
||||||
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Tests (DEV=${{ matrix.dev }})
|
name: Tests (DEV=${{ matrix.dev }})
|
||||||
runs-on: [self-hosted, "${{ matrix.dev == 'METAL' && 'macOS' || matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
runs-on: [self-hosted, "${{ matrix.dev == 'METAL' && 'macOS' || matrix.dev == 'AMD' && 'tinybox' || 'tinyboxgreen' }}"]
|
||||||
@@ -292,7 +322,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['METAL', 'AMD', 'NV']
|
dev: ['METAL', 'AMD', 'NV']
|
||||||
timeout-minutes: 60
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
@@ -305,12 +335,11 @@ jobs:
|
|||||||
- name: Setup (AMD)
|
- name: Setup (AMD)
|
||||||
if: ${{ matrix.dev == 'AMD' }}
|
if: ${{ matrix.dev == 'AMD' }}
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py amd rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py amd rmmod
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py amd kill_pids
|
|
||||||
- name: Setup (NV)
|
- name: Setup (NV)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: sudo lsof -tQ /dev/nvidia* | { xargs -r sudo kill -9 || true; }
|
run: lsof -tQ /dev/nvidia* | { xargs -r kill -9 || true; }
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
if: github.ref == 'refs/heads/update_benchmark_staging'
|
||||||
run: |
|
run: |
|
||||||
@@ -374,7 +403,7 @@ jobs:
|
|||||||
run: python test/external/external_benchmark_multitensor_allreduce.py
|
run: python test/external/external_benchmark_multitensor_allreduce.py
|
||||||
- name: HEVC Decode Benchmark
|
- name: HEVC Decode Benchmark
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
run: IGNORE_BEAM_CACHE=1 VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
||||||
- uses: actions/upload-artifact@v7
|
- uses: actions/upload-artifact@v7
|
||||||
if: ${{ matrix.dev != 'AMD' }}
|
if: ${{ matrix.dev != 'AMD' }}
|
||||||
with:
|
with:
|
||||||
@@ -387,7 +416,7 @@ jobs:
|
|||||||
testusbgpu:
|
testusbgpu:
|
||||||
name: UsbGPU Benchmark
|
name: UsbGPU Benchmark
|
||||||
runs-on: [self-hosted, macOS]
|
runs-on: [self-hosted, macOS]
|
||||||
timeout-minutes: 10
|
timeout-minutes: 3
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
@@ -402,32 +431,66 @@ jobs:
|
|||||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||||
- name: Kill stale pids
|
- name: Kill stale pids
|
||||||
run: |
|
run: |
|
||||||
PYTHONPATH=. ./extra/hcq/hcq_smi.py amd kill_pids
|
./extra/hcq/hcq_smi.py amd kill_pids --sudoless
|
||||||
PYTHONPATH=. ./extra/hcq/hcq_smi.py nv kill_pids
|
./extra/hcq/hcq_smi.py nv kill_pids --sudoless
|
||||||
# since sudo is required for usbgpu on macos, do not write bytecode, as some of the files are owned by root
|
|
||||||
- name: UsbGPU boot time
|
- name: UsbGPU boot time
|
||||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEBUG=2 AM_RESET=1 DEV=USB+AMD time python3.11 test/test_tiny.py TestTiny.test_plus
|
run: GMMU=0 DEBUG=2 AM_RESET=1 DEV=USB+AMD time python3.11 test/test_tiny.py TestTiny.test_plus
|
||||||
- name: UsbGPU tiny tests
|
- name: UsbGPU tiny tests
|
||||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/test_tiny.py
|
run: GMMU=0 DEV=USB+AMD python3.11 test/test_tiny.py
|
||||||
- name: UsbGPU copy speeds
|
- name: UsbGPU copy speeds
|
||||||
run: sudo -E PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
run: SIZE=64000000 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3.11 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
||||||
#- name: UsbGPU openpilot test
|
|
||||||
# run: sudo -E PYTHONPATH=. GMMU=0 DEV=USB+AMD GRAPH_ONE_KERNEL=1 python3.11 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/9118973ed03c1ae1d40cf69a29507ec2cc78efd7/selfdrive/modeld/models/supercombo.onnx
|
|
||||||
- name: UsbGPU (USB4/TB) install script
|
- name: UsbGPU (USB4/TB) install script
|
||||||
run: PYTHONPATH=. sh extra/setup_tinygpu_osx.sh
|
run: sh extra/setup_tinygpu_osx.sh
|
||||||
- name: UsbGPU (USB4/TB) boot time
|
- name: UsbGPU (USB4/TB) boot time
|
||||||
run: PYTHONPATH=. DEBUG=3 DEV=PCI+NV:NAK time python3.11 test/test_tiny.py TestTiny.test_plus
|
run: DEBUG=3 DEV=PCI+NV:NAK time python3.11 test/test_tiny.py TestTiny.test_plus
|
||||||
- name: UsbGPU (USB4/TB) tiny tests
|
- name: UsbGPU (USB4/TB) tiny tests
|
||||||
run: PYTHONPATH=. DEV=PCI+NV:NAK python3.11 test/test_tiny.py
|
run: DEV=PCI+NV:NAK python3.11 test/test_tiny.py
|
||||||
|
|
||||||
testcommalatest:
|
testcomma:
|
||||||
name: comma Benchmark (0.11.2)
|
strategy:
|
||||||
runs-on: [self-hosted, Linux, comma]
|
matrix:
|
||||||
timeout-minutes: 12
|
dev: ['QCOM', 'QCOM:IR3']
|
||||||
|
version: ['0.11.0', '0.11.2']
|
||||||
|
model: ['vision', 'policy', 'supercombo', 'dmonitoring']
|
||||||
|
# exclude non-existent models
|
||||||
|
exclude: [{ version: '0.11.0', model: supercombo }, { version: '0.11.2', model: vision }, { version: '0.11.2', model: policy }]
|
||||||
|
include:
|
||||||
|
- version: '0.11.0'
|
||||||
|
model: vision
|
||||||
|
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
||||||
|
timing: 18
|
||||||
|
- version: '0.11.0'
|
||||||
|
model: policy
|
||||||
|
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_policy.onnx
|
||||||
|
timing: 3.4
|
||||||
|
- version: '0.11.0'
|
||||||
|
model: dmonitoring
|
||||||
|
url: https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/dmonitoring_model.onnx
|
||||||
|
timing: 13
|
||||||
|
- version: '0.11.2'
|
||||||
|
model: supercombo
|
||||||
|
url: https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
||||||
|
timing: 28
|
||||||
|
- dev: QCOM:IR3
|
||||||
|
version: '0.11.2'
|
||||||
|
model: supercombo
|
||||||
|
timing: 29
|
||||||
|
- version: '0.11.2'
|
||||||
|
model: dmonitoring
|
||||||
|
url: https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/3e7b31dfbc0a5234f1baf196513b77fc6af12204b8a8ffe8ee0417e48352f316
|
||||||
|
timing: 12.5
|
||||||
|
fail-fast: false
|
||||||
|
name: openpilot ${{ matrix.version }} compile3 ${{ matrix.model }} (DEV=${{ matrix.dev }})
|
||||||
|
runs-on: [self-hosted, Linux, comma4]
|
||||||
|
timeout-minutes: 5
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
|
env:
|
||||||
|
DEV: ${{ matrix.dev }}
|
||||||
|
ASSERT_MIN_STEP_TIME: ${{ matrix.timing }}
|
||||||
|
BENCHMARK_LOG: ${{ matrix.dev == 'QCOM:IR3' && 'ir3_' || '' }}openpilot_${{ matrix.version }}_${{ matrix.model }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -438,45 +501,10 @@ jobs:
|
|||||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
||||||
- name: reset process replay
|
- name: reset process replay
|
||||||
run: test/external/process_replay/reset.py
|
run: test/external/process_replay/reset.py
|
||||||
- name: openpilot compile3 0.11.2 supercombo
|
- name: compile
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_2_supercombo PYTHONPATH="." ASSERT_MIN_STEP_TIME=26 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
run: FLOAT16=1 IMAGE=1 taskset -c 4-7 python3 examples/openpilot/compile3.py ${{ matrix.url }} openpilot.pkl
|
||||||
- name: openpilot compile3 0.11.2 supercombo (from pickle)
|
- name: run pickle
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_2_supercombo_run_pickle RUN_PICKLE=1 PYTHONPATH="." ASSERT_MIN_STEP_TIME=26 DEV=QCOM taskset -c 4-7 python3 examples/openpilot/compile3.py
|
run: BENCHMARK_LOG="${BENCHMARK_LOG}_run_pickle" RUN_PICKLE=1 taskset -c 4-7 python3 examples/openpilot/compile3.py - openpilot.pkl
|
||||||
- name: IR3 openpilot compile3 0.11.2 supercombo
|
|
||||||
run: BENCHMARK_LOG=ir3_openpilot_0_11_2_supercombo PYTHONPATH="." ASSERT_MIN_STEP_TIME=41 DEV=QCOM:IR3 FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/433f85f956837606ad1f1cbee4aa7e2158ad23c768dea914b20436c97232741b
|
|
||||||
- name: openpilot compile3 0.11.2 dmonitoring
|
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_2_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=11 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://gitlab.com/commaai/openpilot-lfs.git/gitlab-lfs/objects/3e7b31dfbc0a5234f1baf196513b77fc6af12204b8a8ffe8ee0417e48352f316
|
|
||||||
- name: Run process replay tests
|
|
||||||
uses: ./.github/actions/process-replay
|
|
||||||
|
|
||||||
testcommaold:
|
|
||||||
name: comma Benchmark (0.11.0)
|
|
||||||
runs-on: [self-hosted, Linux, comma]
|
|
||||||
timeout-minutes: 12
|
|
||||||
defaults:
|
|
||||||
run:
|
|
||||||
shell: bash -e -o pipefail {0}
|
|
||||||
if: github.repository_owner == 'tinygrad'
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: setup staging db
|
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
|
||||||
run: |
|
|
||||||
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
|
||||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
|
||||||
- name: reset process replay
|
|
||||||
run: test/external/process_replay/reset.py
|
|
||||||
- name: openpilot compile3 0.11.0 driving_vision
|
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_0_vision PYTHONPATH="." ASSERT_MIN_STEP_TIME=17 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
|
||||||
- name: openpilot compile3 0.11.0 driving_vision (from pickle)
|
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_0_vision_run_pickle RUN_PICKLE=1 PYTHONPATH="." ASSERT_MIN_STEP_TIME=17 DEV=QCOM taskset -c 4-7 python3 examples/openpilot/compile3.py
|
|
||||||
- name: IR3 openpilot compile3 0.11.0 driving_vision
|
|
||||||
run: BENCHMARK_LOG=ir3_openpilot_0_11_0_vision PYTHONPATH="." ASSERT_MIN_STEP_TIME=18 DEV=QCOM:IR3 FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_vision.onnx
|
|
||||||
- name: openpilot compile3 0.11.0 driving_policy
|
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_0_policy PYTHONPATH="." ASSERT_MIN_STEP_TIME=3.2 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/driving_policy.onnx
|
|
||||||
- name: openpilot compile3 0.11.0 dmonitoring
|
|
||||||
run: BENCHMARK_LOG=openpilot_0_11_0_dmonitoring PYTHONPATH="." ASSERT_MIN_STEP_TIME=11 DEV=QCOM FLOAT16=1 IMAGE=1 NOLOCALS=1 taskset -c 4-7 python3 examples/openpilot/compile3.py https://github.com/commaai/openpilot/raw/v0.11.0/selfdrive/modeld/models/dmonitoring_model.onnx
|
|
||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
|
|
||||||
@@ -489,15 +517,6 @@ jobs:
|
|||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: setup staging db
|
|
||||||
if: github.ref == 'refs/heads/update_benchmark_staging'
|
|
||||||
run: |
|
|
||||||
echo "CACHEDB=/tmp/staging.db" >> $GITHUB_ENV
|
|
||||||
rm -f /tmp/staging.db /tmp/staging.db-shm /tmp/staging.db-wal
|
|
||||||
- name: reset process replay
|
|
||||||
run: test/external/process_replay/reset.py
|
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
@@ -510,8 +529,8 @@ jobs:
|
|||||||
- name: benchmark MobileNetV2 on DSP
|
- name: benchmark MobileNetV2 on DSP
|
||||||
run: |
|
run: |
|
||||||
# generate quantized weights
|
# generate quantized weights
|
||||||
ln -s /data/home/tiny/tinygrad/extra/datasets/imagenet extra/datasets/imagenet
|
ln -s ~/tinygrad/extra/datasets/imagenet extra/datasets/imagenet
|
||||||
ln -s /data/home/tiny/tinygrad/testsig-*.so .
|
ln -s ~/tinygrad/testsig-*.so .
|
||||||
PYTHONPATH=. DEV=CPU QUANT=1 CNT=0 python3 examples/test_onnx_imagenet.py https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7.onnx /tmp/model.quant.onnx
|
PYTHONPATH=. DEV=CPU QUANT=1 CNT=0 python3 examples/test_onnx_imagenet.py https://github.com/xamcat/mobcat-samples/raw/refs/heads/master/onnx_runtime/InferencingSample/InferencingSample/mobilenetv2-7.onnx /tmp/model.quant.onnx
|
||||||
# benchmark on DSP with NOOPT=1, the devectorizer has issues
|
# benchmark on DSP with NOOPT=1, the devectorizer has issues
|
||||||
PYTHONPATH=. DEV=DSP NOOPT=1 CNT=2 DEBUG=2 python3 examples/test_onnx_imagenet.py /tmp/model.quant.onnx
|
PYTHONPATH=. DEV=DSP NOOPT=1 CNT=2 DEBUG=2 python3 examples/test_onnx_imagenet.py /tmp/model.quant.onnx
|
||||||
@@ -521,7 +540,7 @@ jobs:
|
|||||||
testcommausbgpubenchmark:
|
testcommausbgpubenchmark:
|
||||||
name: UsbGPU Benchmark (comma)
|
name: UsbGPU Benchmark (comma)
|
||||||
runs-on: [self-hosted, Linux, comma4]
|
runs-on: [self-hosted, Linux, comma4]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 10
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
@@ -541,7 +560,7 @@ jobs:
|
|||||||
- name: openpilot run_pickle big_driving_supercombo
|
- name: openpilot run_pickle big_driving_supercombo
|
||||||
run: BENCHMARK_LOG=usbgpu_openpilot_big_driving_supercombo_run_pickle RUN_PICKLE=1 PICKLE_OOB=1 PYTHONPATH="." GMMU=0 DEV=USB+AMD ASSERT_MIN_STEP_TIME=50 python3 examples/openpilot/compile3.py - openpilot.pkl
|
run: BENCHMARK_LOG=usbgpu_openpilot_big_driving_supercombo_run_pickle RUN_PICKLE=1 PICKLE_OOB=1 PYTHONPATH="." GMMU=0 DEV=USB+AMD ASSERT_MIN_STEP_TIME=50 python3 examples/openpilot/compile3.py - openpilot.pkl
|
||||||
- name: Test copy speeds
|
- name: Test copy speeds
|
||||||
run: SIZE=64e6 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
run: SIZE=64000000 PYTHONPATH=. GMMU=0 DEV=USB+AMD python3 test/external/external_test_usb_asm24.py TestDevCopySpeeds
|
||||||
|
|
||||||
driverbenchmarks:
|
driverbenchmarks:
|
||||||
name: PCI Driver Benchmark (DEV=${{ matrix.dev }})
|
name: PCI Driver Benchmark (DEV=${{ matrix.dev }})
|
||||||
@@ -550,7 +569,7 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
dev: ['AMD', 'NV']
|
dev: ['AMD', 'NV']
|
||||||
timeout-minutes: 20
|
timeout-minutes: 5
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -e -o pipefail {0}
|
shell: bash -e -o pipefail {0}
|
||||||
@@ -562,9 +581,8 @@ jobs:
|
|||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Setup
|
- name: Setup
|
||||||
run: |
|
run: |
|
||||||
./extra/amdpci/setup_python_cap.sh
|
./extra/hcq/hcq_smi.py ${{ matrix.dev }} rmmod --expect
|
||||||
./extra/hcq/hcq_smi.py ${{ matrix.dev == 'AMD' && 'amd' || 'nv' }} rmmod
|
./extra/hcq/hcq_smi.py ${{ matrix.dev }} kill_pids --sudoless
|
||||||
./extra/hcq/hcq_smi.py ${{ matrix.dev == 'AMD' && 'amd' || 'nv' }} kill_pids
|
|
||||||
mkdir -p extra/datasets
|
mkdir -p extra/datasets
|
||||||
ln -s /raid/datasets/imagenet extra/datasets/imagenet
|
ln -s /raid/datasets/imagenet extra/datasets/imagenet
|
||||||
- name: setup staging db
|
- name: setup staging db
|
||||||
@@ -599,6 +617,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyDefaulttoCPUJit
|
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyDefaulttoCPUJit
|
||||||
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyCPUtoDefaultJit
|
GRAPH_ONE_KERNEL=1 NSZ=8192 python3 test/speed/external_test_copy_speed.py TestCopySpeed.testCopyCPUtoDefaultJit
|
||||||
|
- name: HEVC Decode Benchmark
|
||||||
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
|
run: IGNORE_BEAM_CACHE=1 VALIDATE=1 MAX_FRAMES=100 ASSERT_FPS=1400 JITBEAM=1 PYTHONPATH=. python3 extra/hevc/decode.py
|
||||||
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
- name: Run 10 MLPerf ResNet50 training steps (1 gpu)
|
||||||
if: ${{ matrix.dev == 'NV' }}
|
if: ${{ matrix.dev == 'NV' }}
|
||||||
run: BENCHMARK_LOG=resnet_10steps MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
run: BENCHMARK_LOG=resnet_10steps MNISTMOCK=1 DEFAULT_FLOAT=HALF BENCHMARK=10 BS=256 GPUS=1 MODEL=resnet python3 examples/mlperf/model_train.py
|
||||||
@@ -621,12 +642,12 @@ jobs:
|
|||||||
llvmspeed:
|
llvmspeed:
|
||||||
name: LLVM Speed
|
name: LLVM Speed
|
||||||
runs-on: [self-hosted, Linux, tinyboxrandom]
|
runs-on: [self-hosted, Linux, tinyboxrandom]
|
||||||
timeout-minutes: 20
|
timeout-minutes: 10
|
||||||
if: github.repository_owner == 'tinygrad'
|
if: github.repository_owner == 'tinygrad'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Speed Test
|
- name: Speed Test
|
||||||
run: DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
|
run: DEV=CPU:LLVM python3 test/speed/external_test_speed_v_torch.py
|
||||||
- name: Speed Test (BEAM=2)
|
- name: Speed Test (BEAM=2)
|
||||||
run: BEAM=2 DEV=CPU:LLVM THREADS=0 python3 test/speed/external_test_speed_v_torch.py
|
run: IGNORE_BEAM_CACHE=1 BEAM=2 DEV=CPU:LLVM python3 test/speed/external_test_speed_v_torch.py
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Configure Git Credentials
|
- name: Configure Git Credentials
|
||||||
|
|||||||
+1
-33
@@ -166,7 +166,7 @@ jobs:
|
|||||||
uses: ./.github/actions/setup-tinygrad
|
uses: ./.github/actions/setup-tinygrad
|
||||||
with:
|
with:
|
||||||
key: windows-${{ matrix.dev }}-minimal
|
key: windows-${{ matrix.dev }}-minimal
|
||||||
deps: testing_unit
|
deps: testing_minimal
|
||||||
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }}
|
pydeps: ${{ matrix.dev == 'WEBGPU' && 'dawn-python' || '' }}
|
||||||
- name: Set env
|
- name: Set env
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -179,35 +179,3 @@ jobs:
|
|||||||
- name: Run test_tiny
|
- name: Run test_tiny
|
||||||
shell: bash
|
shell: bash
|
||||||
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
run: python -m pytest -n=auto test/test_tiny.py --durations=20
|
||||||
|
|
||||||
|
|
||||||
qcomclcompiletests:
|
|
||||||
name: Compile-only (QCOM CL)
|
|
||||||
runs-on: ubuntu-24.04-arm
|
|
||||||
timeout-minutes: 15
|
|
||||||
steps:
|
|
||||||
- name: Checkout Code
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
- name: Setup Environment
|
|
||||||
uses: ./.github/actions/setup-tinygrad
|
|
||||||
with:
|
|
||||||
key: compile-qcomcl
|
|
||||||
deps: testing_unit
|
|
||||||
tinydreno: 'true'
|
|
||||||
- name: Set env
|
|
||||||
shell: bash
|
|
||||||
run: printf "DEV=NULL:QCOMCL:a630\nNULL_ALLOW_COPYOUT=1" >> $GITHUB_ENV
|
|
||||||
- name: Run test_ops
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
|
||||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
|
||||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
|
||||||
- name: Run test_ops (IMAGE)
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
IMAGE: 1
|
|
||||||
DEV: "NULL:QCOMCL:a630,IMAGE_PITCH_ALIGNMENT=64"
|
|
||||||
run: |
|
|
||||||
DEBUG=4 python test/backend/test_ops.py TestOps.test_gemm | grep read_imagef
|
|
||||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
|
||||||
|
|||||||
+1
-1
@@ -10,7 +10,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
|
|||||||
+3
-3
@@ -10,7 +10,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
checkbranch:
|
checkbranch:
|
||||||
name: Check PR Branch status
|
name: Check PR Branch status
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
outputs:
|
outputs:
|
||||||
branchstat: ${{ steps.brstat.outputs.stat}}
|
branchstat: ${{ steps.brstat.outputs.stat}}
|
||||||
steps:
|
steps:
|
||||||
@@ -44,7 +44,7 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs: checkbranch
|
needs: checkbranch
|
||||||
if: needs.checkbranch.outputs.branchstat == 'false'
|
if: needs.checkbranch.outputs.branchstat == 'false'
|
||||||
steps:
|
steps:
|
||||||
@@ -87,7 +87,7 @@ jobs:
|
|||||||
name: Core Library Line Difference
|
name: Core Library Line Difference
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-24.04
|
||||||
needs: checkbranch
|
needs: checkbranch
|
||||||
if: needs.checkbranch.outputs.branchstat == 'true'
|
if: needs.checkbranch.outputs.branchstat == 'true'
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
+66
-53
@@ -21,7 +21,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
docs:
|
docs:
|
||||||
name: Docs
|
name: Docs
|
||||||
runs-on: &linux ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
CHECK_OOB: 0
|
CHECK_OOB: 0
|
||||||
@@ -31,8 +31,7 @@ jobs:
|
|||||||
- name: Setup Environment
|
- name: Setup Environment
|
||||||
uses: ./.github/actions/setup-tinygrad
|
uses: ./.github/actions/setup-tinygrad
|
||||||
with:
|
with:
|
||||||
deps: docs
|
deps: "docs testing_minimal"
|
||||||
pydeps: "capstone torch"
|
|
||||||
- name: Build wheel and show size
|
- name: Build wheel and show size
|
||||||
run: |
|
run: |
|
||||||
uv build --wheel
|
uv build --wheel
|
||||||
@@ -61,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
torchbackend:
|
torchbackend:
|
||||||
name: Torch Backend Tests
|
name: Torch Backend Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -73,10 +72,7 @@ jobs:
|
|||||||
deps: testing_unit
|
deps: testing_unit
|
||||||
pydeps: "pillow torchvision expecttest"
|
pydeps: "pillow torchvision expecttest"
|
||||||
llvm: 'true'
|
llvm: 'true'
|
||||||
- name: Install ninja
|
ninja: 'true'
|
||||||
run: |
|
|
||||||
sudo apt update || true
|
|
||||||
sudo apt install -y --no-install-recommends ninja-build
|
|
||||||
- name: Test ResNet-18
|
- name: Test ResNet-18
|
||||||
run: DEBUG=2 python3 extra/torch_backend/example.py
|
run: DEBUG=2 python3 extra/torch_backend/example.py
|
||||||
- name: Test one op in torch tests
|
- name: Test one op in torch tests
|
||||||
@@ -86,9 +82,26 @@ jobs:
|
|||||||
- name: Custom tests
|
- name: Custom tests
|
||||||
run: DEV=CPU:LLVM GPUS=4 TINY_BACKEND=1 python3 -m pytest -nauto extra/torch_backend/test.py extra/torch_backend/test_inplace.py extra/torch_backend/test_multigpu.py extra/torch_backend/test_kernel_fusion.py --durations=20
|
run: DEV=CPU:LLVM GPUS=4 TINY_BACKEND=1 python3 -m pytest -nauto extra/torch_backend/test.py extra/torch_backend/test_inplace.py extra/torch_backend/test_multigpu.py extra/torch_backend/test_kernel_fusion.py --durations=20
|
||||||
|
|
||||||
|
torchbackendtrain:
|
||||||
|
name: Torch Backend Training
|
||||||
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
- name: Setup Environment
|
||||||
|
uses: ./.github/actions/setup-tinygrad
|
||||||
|
with:
|
||||||
|
key: torch-backend-pillow-torchvision-et-pt
|
||||||
|
deps: testing_unit
|
||||||
|
llvm: 'true'
|
||||||
|
ninja: 'true'
|
||||||
|
- name: Test beautiful_mnist in torch with TINY_BACKEND
|
||||||
|
run: STEPS=20 DEV=CPU TARGET_EVAL_ACC_PCT=90.0 MAX_BUFFER_SIZE=0 TINY_BACKEND=1 python3 examples/other_mnist/beautiful_mnist_torch.py
|
||||||
|
|
||||||
bepython:
|
bepython:
|
||||||
name: Python Backend
|
name: Python Backend
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -126,7 +139,7 @@ jobs:
|
|||||||
|
|
||||||
linter:
|
linter:
|
||||||
name: Linters
|
name: Linters
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -157,7 +170,7 @@ jobs:
|
|||||||
|
|
||||||
nulltest:
|
nulltest:
|
||||||
name: Null Tests
|
name: Null Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -191,7 +204,7 @@ jobs:
|
|||||||
|
|
||||||
unittest:
|
unittest:
|
||||||
name: Unit Tests
|
name: Unit Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -220,7 +233,7 @@ jobs:
|
|||||||
- name: Run process replay tests
|
- name: Run process replay tests
|
||||||
uses: ./.github/actions/process-replay
|
uses: ./.github/actions/process-replay
|
||||||
- name: Repo line count <= 26000 lines
|
- name: Repo line count <= 26000 lines
|
||||||
run: MAX_LINE_COUNT=26000 python sz.py
|
run: MAX_LINE_COUNT=26500 python sz.py
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
strategy:
|
strategy:
|
||||||
@@ -228,7 +241,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
group: [1, 2]
|
group: [1, 2]
|
||||||
name: SPEC=2 (${{ matrix.group }})
|
name: SPEC=2 (${{ matrix.group }})
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -244,7 +257,7 @@ jobs:
|
|||||||
|
|
||||||
fuzzing:
|
fuzzing:
|
||||||
name: Fuzzing
|
name: Fuzzing
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -260,7 +273,7 @@ jobs:
|
|||||||
|
|
||||||
testopenclimage:
|
testopenclimage:
|
||||||
name: CL IMAGE Tests
|
name: CL IMAGE Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -280,7 +293,7 @@ jobs:
|
|||||||
|
|
||||||
testopenpilot:
|
testopenpilot:
|
||||||
name: openpilot Compile Tests
|
name: openpilot Compile Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -309,7 +322,7 @@ jobs:
|
|||||||
|
|
||||||
testonnxcpu:
|
testonnxcpu:
|
||||||
name: ONNX (CPU) Tests
|
name: ONNX (CPU) Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -328,7 +341,7 @@ jobs:
|
|||||||
|
|
||||||
testoptim:
|
testoptim:
|
||||||
name: Optimization Tests
|
name: Optimization Tests
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -360,7 +373,7 @@ jobs:
|
|||||||
|
|
||||||
testllm:
|
testllm:
|
||||||
name: Test LLM
|
name: Test LLM
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
env:
|
env:
|
||||||
CHECK_OOB: 0
|
CHECK_OOB: 0
|
||||||
@@ -377,17 +390,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
parallel --link --tagstring '[{1}]' '{2}' \
|
parallel --link --tagstring '[{1}]' '{2}' \
|
||||||
::: llama 'llama q4' qwen3.5 qwen \
|
::: llama 'llama q4' qwen3.5 qwen \
|
||||||
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model llama3.2:1b | tee /dev/stderr | grep -i rooster' \
|
::: $'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b | tee /dev/stderr | grep -i rooster' \
|
||||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model llama3.2:1b-q4 | tee /dev/stderr | grep -i rooster' \
|
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model llama3.2:1b-q4 | tee /dev/stderr | grep -i rooster' \
|
||||||
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --model qwen3.5:0.8b | tee /dev/stderr | grep -i rooster' \
|
$'echo "What\'s a male chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3.5:0.8b | tee /dev/stderr | grep -i rooster' \
|
||||||
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --model qwen3:0.6b | tee /dev/stderr | grep -i hen'
|
$'echo "What\'s a female chicken called? Answer with only one word." | python3 -m tinygrad.llm --no_chat_template --model qwen3:0.6b | tee /dev/stderr | grep -i hen'
|
||||||
# NOTE: qwen is dumb and only knows about female chickens
|
# NOTE: qwen is dumb and only knows about female chickens
|
||||||
|
|
||||||
# ****** Models Tests ******
|
# ****** Models Tests ******
|
||||||
|
|
||||||
testmodels:
|
testmodels:
|
||||||
name: Models
|
name: Models
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -407,7 +420,7 @@ jobs:
|
|||||||
|
|
||||||
testdsp:
|
testdsp:
|
||||||
name: Linux (DSP)
|
name: Linux (DSP)
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -435,7 +448,7 @@ jobs:
|
|||||||
- 'WEBGPU'
|
- 'WEBGPU'
|
||||||
|
|
||||||
name: Linux (DEV=${{ matrix.dev }})
|
name: Linux (DEV=${{ matrix.dev }})
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -461,7 +474,7 @@ jobs:
|
|||||||
|
|
||||||
testamdasm:
|
testamdasm:
|
||||||
name: AMD ASM IDE
|
name: AMD ASM IDE
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
env:
|
env:
|
||||||
DEV: MOCKKFD+AMD
|
DEV: MOCKKFD+AMD
|
||||||
@@ -491,7 +504,7 @@ jobs:
|
|||||||
- name: Run AMD renderer tests (AMD:LLVM)
|
- name: Run AMD renderer tests (AMD:LLVM)
|
||||||
run: DEV=MOCKKFD+AMD:LLVM python -m pytest -n=auto test/amd/ --durations 20
|
run: DEV=MOCKKFD+AMD:LLVM python -m pytest -n=auto test/amd/ --durations 20
|
||||||
- name: Run SQTT profiling tests
|
- name: Run SQTT profiling tests
|
||||||
run: PROFILE=1 SQTT=1 python3 -m pytest -n=auto test/amd/test_sqtt_profiler.py
|
run: VIZ=-2 python3 -m pytest -n=auto test/amd/test_sqtt_profiler.py
|
||||||
- name: Run AMD emulated tests on NULL backend
|
- name: Run AMD emulated tests on NULL backend
|
||||||
env:
|
env:
|
||||||
AMD: 0
|
AMD: 0
|
||||||
@@ -507,7 +520,7 @@ jobs:
|
|||||||
|
|
||||||
hcq2:
|
hcq2:
|
||||||
name: hcq2
|
name: hcq2
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
@@ -521,16 +534,15 @@ jobs:
|
|||||||
- name: Run HCQ2 tests
|
- name: Run HCQ2 tests
|
||||||
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/test_tiny.py
|
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/test_tiny.py
|
||||||
- name: Run HCQ2 multi-device tests
|
- name: Run HCQ2 multi-device tests
|
||||||
run: |
|
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python -m pytest -n=auto test/backend/test_multitensor.py
|
||||||
HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/unit/test_multitensor.py \
|
|
||||||
TestMultiTensor.test_simple_add TestMultiTensor.test_shard_reduce \
|
|
||||||
TestMultiTensor.test_backward_sum TestMultiTensor.test_matmul_shard_0_0
|
|
||||||
- name: Run HCQ2 JIT tests
|
- name: Run HCQ2 JIT tests
|
||||||
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/unit/test_jit.py
|
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python test/unit/test_jit.py
|
||||||
|
- name: Run HCQ2 unit tests
|
||||||
|
run: HCQ_RUNTIME_DEV=PYTHON HCQ2=1 DEV=MOCKKFD+AMD FORWARD_ONLY=1 PYTHONPATH=. python -m pytest test/device/test_hcq2.py
|
||||||
|
|
||||||
testmockam:
|
testmockam:
|
||||||
name: Linux (am)
|
name: Linux (am)
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
env:
|
env:
|
||||||
DEV: MOCKPCI+AMD
|
DEV: MOCKPCI+AMD
|
||||||
@@ -566,7 +578,7 @@ jobs:
|
|||||||
arch: [gfx1100, gfx1201, gfx950]
|
arch: [gfx1100, gfx1201, gfx950]
|
||||||
|
|
||||||
name: Linux (${{ matrix.backend }} ${{ matrix.arch }})
|
name: Linux (${{ matrix.backend }} ${{ matrix.arch }})
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
env:
|
env:
|
||||||
DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }}
|
DEV: MOCKKFD+AMD:${{ matrix.backend == 'amdllvm' && 'LLVM' || '' }}:${{ matrix.arch }}
|
||||||
@@ -589,7 +601,7 @@ jobs:
|
|||||||
if: ${{ matrix.backend == 'amd' && matrix.arch == 'gfx950' }}
|
if: ${{ matrix.backend == 'amd' && matrix.arch == 'gfx950' }}
|
||||||
run: PYTHONPATH=. DEV=NULL:HIP:gfx950 MXFP4=1 LLAMA_LAYERS=2 BENCHMARK=3 NULL_ALLOW_COPYOUT=1 NO_HIPCC=1 ROCM_PATH=/opt/rocm JITBEAM=0 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/profile.sh
|
run: PYTHONPATH=. DEV=NULL:HIP:gfx950 MXFP4=1 LLAMA_LAYERS=2 BENCHMARK=3 NULL_ALLOW_COPYOUT=1 NO_HIPCC=1 ROCM_PATH=/opt/rocm JITBEAM=0 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/profile.sh
|
||||||
- name: Run pytest (amd)
|
- name: Run pytest (amd)
|
||||||
run: python -m pytest -n=auto test/backend/test_ops.py test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_linearizer.py test/backend/test_randomness.py test/backend/test_jit.py test/backend/test_graph.py test/backend/test_multitensor.py test/device/test_hcq.py test/external/external_test_am.py test/backend/test_asm_gemm.py::TestAsmGEMM test/opt/test_tensor_cores.py --durations=20
|
run: python -m pytest -n=auto test/backend/test_ops.py test/backend/test_dtype.py test/backend/test_dtype_alu.py test/backend/test_linearizer.py test/backend/test_randomness.py test/backend/test_jit.py test/backend/test_graph.py test/backend/test_multitensor.py test/device/test_hcq.py test/external/external_test_am.py test/backend/test_asm_gemm.py::TestAsmGEMM --durations=20
|
||||||
- name: Run disk copy tests
|
- name: Run disk copy tests
|
||||||
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
|
run: python -m pytest test/unit/test_disk_tensor.py -k test_copy_from_disk
|
||||||
- name: Run TRANSCENDENTAL math
|
- name: Run TRANSCENDENTAL math
|
||||||
@@ -604,7 +616,7 @@ jobs:
|
|||||||
backend: [ptx, nv]
|
backend: [ptx, nv]
|
||||||
|
|
||||||
name: Linux (${{ matrix.backend }})
|
name: Linux (${{ matrix.backend }})
|
||||||
runs-on: *linux
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 20
|
timeout-minutes: 20
|
||||||
env:
|
env:
|
||||||
FORWARD_ONLY: 1
|
FORWARD_ONLY: 1
|
||||||
@@ -638,10 +650,17 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
backend: [ir3, nak]
|
dev:
|
||||||
name: Compile-only (${{ matrix.backend }})
|
- 'NULL:IR3:a630'
|
||||||
runs-on: *linux
|
- 'NULL:QCOMCL:a630'
|
||||||
|
- 'NULL:NAK:sm_120'
|
||||||
|
name: Compile-only (DEV=${{ matrix.dev }})
|
||||||
|
runs-on: ${{ github.repository == 'tinygrad/tinygrad' && github.event_name == 'pull_request' && github.event.pull_request.author_association == 'COLLABORATOR' && 'namespace-profile-tinygrad' || 'ubuntu-24.04' }}
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
env:
|
||||||
|
NULL_ALLOW_COPYOUT: 1
|
||||||
|
DEV: ${{ matrix.dev }}${{ contains(matrix.dev, 'a630') && ',IMAGE_PITCH_ALIGNMENT=64' || '' }}
|
||||||
|
IMAGE: ${{ contains(matrix.dev, 'a630') && '1' || '0' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
@@ -650,21 +669,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
key: compile-${{ matrix.backend }}
|
key: compile-${{ matrix.backend }}
|
||||||
deps: "testing_unit mesa"
|
deps: "testing_unit mesa"
|
||||||
- name: Set env
|
qemu: ${{ contains(matrix.dev, 'QCOMCL') }}
|
||||||
|
- name: Test IMAGE
|
||||||
shell: bash
|
shell: bash
|
||||||
run: printf "NULL_ALLOW_COPYOUT=1\n${{ matrix.backend == 'ir3' && 'DEV=NULL:IR3:a630' || matrix.backend == 'nak' && 'DEV=NULL:NAK:sm_120' }}" >> $GITHUB_ENV
|
if: contains(matrix.dev, 'a630')
|
||||||
|
run: DEBUG=7 python3 test/backend/test_ops.py TestOps.test_gemm | grep isam
|
||||||
- name: Run test_ops
|
- name: Run test_ops
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
python -c "from tinygrad import Device; assert Device.DEFAULT == 'NULL'"
|
||||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_add
|
||||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
# QCOMCL compiles in qemu, too slow for parallel workers
|
||||||
- name: Run test_ops (IMAGE)
|
${{ contains(matrix.dev, 'QCOMCL') && 'PARALLEL=0' || '' }} python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
||||||
if: matrix.backend == 'ir3'
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
IMAGE: 1
|
|
||||||
DEV: "NULL:IR3:a630,IMAGE_PITCH_ALIGNMENT=64"
|
|
||||||
run: |
|
|
||||||
DEBUG=4 python3 test/backend/test_ops.py TestOps.test_gemm | grep image_load
|
|
||||||
python -m pytest -n=auto test/backend/test_ops.py --durations=20
|
|
||||||
|
|||||||
@@ -69,3 +69,4 @@ mutants
|
|||||||
dagre/
|
dagre/
|
||||||
graphlib/
|
graphlib/
|
||||||
uv.lock
|
uv.lock
|
||||||
|
pi_session_window0.jsonl
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ repos:
|
|||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
- id: tests
|
- id: tests
|
||||||
name: comprehensive test suite
|
name: comprehensive test suite
|
||||||
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/backend/test_ops.py test/backend/test_schedule.py test/unit/test_assign.py test/backend/test_tensor.py test/backend/test_jit.py test/unit/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/unit/test_helpers.py
|
entry: env OMP_NUM_THREADS=1 SKIP_SLOW_TEST=1 PYTHONPATH="." python3 -m pytest -n=6 test/backend/test_ops.py test/backend/test_schedule.py test/backend/test_assign.py test/backend/test_tensor.py test/backend/test_jit.py test/unit/test_schedule_cache.py test/null/test_pattern_matcher.py test/null/test_uop_symbolic.py test/unit/test_helpers.py
|
||||||
language: system
|
language: system
|
||||||
always_run: true
|
always_run: true
|
||||||
pass_filenames: false
|
pass_filenames: false
|
||||||
|
|||||||
@@ -4,3 +4,4 @@
|
|||||||
- Run `python -m mypy tinygrad/` to typecheck
|
- Run `python -m mypy tinygrad/` to typecheck
|
||||||
- Run `python -m ruff check .` to lint
|
- Run `python -m ruff check .` to lint
|
||||||
- Read `./tinygrad/viz/README.md` for profiling and debugging rewrite rules
|
- Read `./tinygrad/viz/README.md` for profiling and debugging rewrite rules
|
||||||
|
- Do not do amend commits. Always do a new commit if a force push to origin would be required.
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ Documentation along with a quick start guide can be found on the [docs website](
|
|||||||
```python
|
```python
|
||||||
from tinygrad import Tensor
|
from tinygrad import Tensor
|
||||||
|
|
||||||
x = Tensor.eye(3)
|
x = Tensor.eye(3).clone() # clone to make it a buffer
|
||||||
y = Tensor([[2.0,0,-2.0]])
|
y = Tensor([[2.0,0,-2.0]])
|
||||||
z = y.matmul(x).sum()
|
z = y.matmul(x).sum()
|
||||||
z.backward()
|
z.backward()
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
8611fe22a7fcc7d1928bbde19ded66277cb12f3e
|
f6fc4e3f2c3db5fae1e19cbfbc3ad9fc579a12ae
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import os, pytest, signal, threading
|
|||||||
|
|
||||||
@pytest.hookimpl(wrapper=True)
|
@pytest.hookimpl(wrapper=True)
|
||||||
def pytest_runtest_call(item):
|
def pytest_runtest_call(item):
|
||||||
t = threading.Timer(int(os.getenv("TEST_TIMEOUT", 300)), os.kill, args=(os.getpid(), signal.SIGABRT))
|
t = threading.Timer(int(os.getenv("TEST_TIMEOUT", 90)), os.kill, args=(os.getpid(), signal.SIGABRT))
|
||||||
t.start()
|
t.start()
|
||||||
try: yield
|
try: yield
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ def example_5_custom_assembly(a:Tensor, correct):
|
|||||||
offset_dwords = (self.labels[inst._target] - inst._pos - inst.size()) // 4
|
offset_dwords = (self.labels[inst._target] - inst._pos - inst.size()) // 4
|
||||||
if not -32768 <= offset_dwords <= 32767: raise ValueError(f"branch to '{inst._target}' offset {offset_dwords} exceeds simm16 range")
|
if not -32768 <= offset_dwords <= 32767: raise ValueError(f"branch to '{inst._target}' offset {offset_dwords} exceeds simm16 range")
|
||||||
inst.simm16 = offset_dwords
|
inst.simm16 = offset_dwords
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in self.instructions]))))
|
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in self.instructions]))))
|
||||||
|
|
||||||
CU_COUNT = 32
|
CU_COUNT = 32
|
||||||
LANES = 64
|
LANES = 64
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ In `kernel.py` we have a set of `OptOps`, these control the parameters of the sp
|
|||||||
|
|
||||||
The main bottleneck in most kernels is accessing memory. In a freshman algorithms class, you'll learn about cache aware matrix multiplication, and this is all forms of that. While the same math is run, the order in which you run it can have large impacts on the speed depending on if the data you are loading. OptOps will change this order.
|
The main bottleneck in most kernels is accessing memory. In a freshman algorithms class, you'll learn about cache aware matrix multiplication, and this is all forms of that. While the same math is run, the order in which you run it can have large impacts on the speed depending on if the data you are loading. OptOps will change this order.
|
||||||
|
|
||||||
Memory, even cache, is often much slower than accessing the register file. The amount of times data is used in math is called the "arithmetic intensity". For operations like BS=1 GEMV, the arithmetic intensity is 1, but for GEMMs and convs it can be much higher. OptOps like UPCAST and UNROLL can increase this, but be careful of making them too large, as if there's too much register pressure on the GPU the warp scheduler may not be able to fit many warps, or even worse, it could be spilling to local memory.
|
Memory, even cache, is often much slower than accessing the register file. The amount of times data is used in math is called the "arithmetic intensity". For operations like BS=1 GEMV, the arithmetic intensity is 1, but for GEMMs and convs it can be much higher. Splitting an axis into UPCAST can increase this, but be careful of making them too large, as if there's too much register pressure on the GPU the warp scheduler may not be able to fit many warps, or even worse, it could be spilling to local memory.
|
||||||
|
|
||||||
4090s have 1 TB/s of ram bandwidth and ~160 TFLOPS of compute, so you need to use each loaded value ~100 times. The L1 cache has around 40 TB/s of bandwidth, so in order to get full compute utilization you need to use each value ~4 times.
|
4090s have 1 TB/s of ram bandwidth and ~160 TFLOPS of compute, so you need to use each loaded value ~100 times. The L1 cache has around 40 TB/s of bandwidth, so in order to get full compute utilization you need to use each value ~4 times.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from multiprocessing import Queue, Process, shared_memory, connection, Lock
|
|||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from tinygrad import dtypes, Tensor
|
from tinygrad import dtypes, Tensor
|
||||||
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX, NUM_CPU_THREADS
|
from tinygrad.helpers import getenv, prod, Context, round_up, tqdm, OSX, CPU_COUNT
|
||||||
from tinygrad.nn.state import TensorIO
|
from tinygrad.nn.state import TensorIO
|
||||||
|
|
||||||
### ResNet
|
### ResNet
|
||||||
@@ -131,7 +131,7 @@ def batch_load_resnet(batch_size=64, val=False, shuffle=True, seed=None, pad_fir
|
|||||||
else: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name}")
|
else: X = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name}")
|
||||||
Y = [None] * (batch_size*BATCH_COUNT)
|
Y = [None] * (batch_size*BATCH_COUNT)
|
||||||
|
|
||||||
for _ in range(NUM_CPU_THREADS.value):
|
for _ in range(CPU_COUNT):
|
||||||
p = Process(target=loader_process, args=(q_in, q_out, X, seed))
|
p = Process(target=loader_process, args=(q_in, q_out, X, seed))
|
||||||
p.daemon = True
|
p.daemon = True
|
||||||
p.start()
|
p.start()
|
||||||
@@ -212,7 +212,7 @@ def batch_load_train_bert(BS:int, seed:int|None=None):
|
|||||||
rng.shuffle(fs)
|
rng.shuffle(fs)
|
||||||
train_files.append(fs.pop(0))
|
train_files.append(fs.pop(0))
|
||||||
|
|
||||||
cycle_length = min(NUM_CPU_THREADS.value, len(train_files))
|
cycle_length = min(CPU_COUNT, len(train_files))
|
||||||
assert cycle_length > 0, "cycle_length must be greater than 0"
|
assert cycle_length > 0, "cycle_length must be greater than 0"
|
||||||
|
|
||||||
dataset = InterleavedDataset(train_files, cycle_length)
|
dataset = InterleavedDataset(train_files, cycle_length)
|
||||||
@@ -301,7 +301,7 @@ def batch_load_unet3d(preprocessed_dataset_dir:Path, batch_size:int=6, val:bool=
|
|||||||
X = Tensor.empty(*sz, dtype=dtypes.float32, device=f"disk:/dev/shm/{shm_name_x}")
|
X = Tensor.empty(*sz, dtype=dtypes.float32, device=f"disk:/dev/shm/{shm_name_x}")
|
||||||
Y = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name_y}")
|
Y = Tensor.empty(*sz, dtype=dtypes.uint8, device=f"disk:/dev/shm/{shm_name_y}")
|
||||||
|
|
||||||
for _ in range(NUM_CPU_THREADS.value):
|
for _ in range(CPU_COUNT):
|
||||||
proc = Process(target=load_unet3d_data, args=(preprocessed_dataset_dir, seed, queue_in, queue_out, X, Y))
|
proc = Process(target=load_unet3d_data, args=(preprocessed_dataset_dir, seed, queue_in, queue_out, X, Y))
|
||||||
proc.daemon = True
|
proc.daemon = True
|
||||||
proc.start()
|
proc.start()
|
||||||
@@ -437,7 +437,7 @@ def batch_load_retinanet(dataset, val:bool, base_dir:Path, batch_size:int=32, sh
|
|||||||
dataset_iter = iter(image_ids)
|
dataset_iter = iter(image_ids)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for _ in range(NUM_CPU_THREADS.value):
|
for _ in range(CPU_COUNT):
|
||||||
proc = Process(
|
proc = Process(
|
||||||
target=load_retinanet_data,
|
target=load_retinanet_data,
|
||||||
args=(base_dir, val, queue_in, queue_out, imgs, boxes, labels),
|
args=(base_dir, val, queue_in, queue_out, imgs, boxes, labels),
|
||||||
|
|||||||
@@ -1667,15 +1667,14 @@ def train_llama3():
|
|||||||
def train_gptoss():
|
def train_gptoss():
|
||||||
from examples.mlperf.models.gpt_oss import GPTOSS, GPT_OSS_20B, apply_grad, FP8_DTYPE
|
from examples.mlperf.models.gpt_oss import GPTOSS, GPT_OSS_20B, apply_grad, FP8_DTYPE
|
||||||
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
from examples.mlperf.lr_schedulers import CosineAnnealingLRWithWarmup
|
||||||
from examples.mlperf.optim import GradAccClipAdamW, GradAccClipAdamWGroup, clip_grads
|
from examples.mlperf.optim import GradAccClipAdamW, GradAccClipAdamWGroup, fclip_grads
|
||||||
|
|
||||||
BENCHMARK = getenv("BENCHMARK")
|
BENCHMARK = getenv("BENCHMARK")
|
||||||
|
|
||||||
config = {}
|
config = {}
|
||||||
BASEDIR = config["BASEDIR"] = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
BASEDIR = config["BASEDIR"] = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
||||||
BS = config["BS"] = getenv("BS", 16)
|
BS = config["BS"] = getenv("BS", 16)
|
||||||
grad_acc = config["GRADIENT_ACC_STEPS"] = getenv("GRADIENT_ACC_STEPS", 1)
|
GBS = config["GLOBAL_BATCH_SIZE"] = BS
|
||||||
GBS = config["GLOBAL_BATCH_SIZE"] = BS * grad_acc
|
|
||||||
SEED = config["SEED"] = getenv("SEED", 5760)
|
SEED = config["SEED"] = getenv("SEED", 5760)
|
||||||
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
|
DATA_SEED = config["DATA_SEED"] = getenv("DATA_SEED", SEED)
|
||||||
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
SEQLEN = config["SEQLEN"] = getenv("SEQLEN", 8192)
|
||||||
@@ -1737,13 +1736,13 @@ def train_gptoss():
|
|||||||
params_wd = [p for p in params if p.ndim >= 3]
|
params_wd = [p for p in params if p.ndim >= 3]
|
||||||
params_no_wd = [p for p in params if p.ndim < 3]
|
params_no_wd = [p for p in params if p.ndim < 3]
|
||||||
optim = GradAccClipAdamWGroup(
|
optim = GradAccClipAdamWGroup(
|
||||||
GradAccClipAdamW(params_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=grad_acc, device=optim_device),
|
GradAccClipAdamW(params_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=opt_adamw_weight_decay, grad_acc=1, device=optim_device),
|
||||||
GradAccClipAdamW(params_no_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=0.0, grad_acc=grad_acc, device=optim_device),
|
GradAccClipAdamW(params_no_wd, lr=0.0, b1=opt_adamw_beta_1, b2=opt_adamw_beta_2, eps=opt_adamw_epsilon, weight_decay=0.0, grad_acc=1, device=optim_device),
|
||||||
)
|
)
|
||||||
|
|
||||||
for p in optim.params:
|
for p in optim.params:
|
||||||
grad_dtype = dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype
|
p.grad = p.zeros_like(dtype=dtypes.bfloat16 if p.dtype == FP8_DTYPE else p.dtype).contiguous()
|
||||||
p.grad = p.zeros_like(dtype=grad_dtype).contiguous()
|
if getattr(p, "_zero2", False): p.grad = optim.optimizers[0]._zero_shard(p.grad)
|
||||||
grads = [p.grad for p in optim.params]
|
grads = [p.grad for p in optim.params]
|
||||||
|
|
||||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale
|
from extra.gemm.cdna_asm_gemm import _mx_block_scale
|
||||||
@@ -1770,31 +1769,32 @@ def train_gptoss():
|
|||||||
|
|
||||||
@TinyJit
|
@TinyJit
|
||||||
@Context(TRAINING=1)
|
@Context(TRAINING=1)
|
||||||
def minibatch(tokens:Tensor):
|
def step(tokens:Tensor):
|
||||||
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
if is_dp: tokens = tokens.to(None).shard(device, 0)
|
||||||
if not is_sharding: tokens = tokens.to(None)
|
if not is_sharding: tokens = tokens.to(None)
|
||||||
|
|
||||||
logits:Tensor = model(tokens[:, :-1], save=True)
|
logits:Tensor = model(tokens[:, :-1], save=True)
|
||||||
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
if getenv("FUSED_CE", 0):
|
||||||
|
from extra.llama_kernels.fused_ce import fused_ce_loss
|
||||||
|
loss = fused_ce_loss(logits.cast(dtypes.bfloat16), tokens[:, 1:], label_smoothing=0.0)
|
||||||
|
else:
|
||||||
|
loss = logits.sparse_categorical_crossentropy(tokens[:, 1:])
|
||||||
|
|
||||||
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
for g, new_g in zip(grads, loss.gradient(*optim.params)):
|
||||||
apply_grad(g, new_g.uop)
|
apply_grad(g, new_g.uop)
|
||||||
|
|
||||||
loss_cpu = loss.flatten().float().to("CPU")
|
Tensor.realize(loss, *grads)
|
||||||
return loss_cpu.realize(*grads)
|
|
||||||
|
|
||||||
@TinyJit
|
clipped_grads, grad_norm = fclip_grads(grads, 1.0)
|
||||||
def optim_step():
|
optim.fstep(clipped_grads, grad_norm)
|
||||||
grad_norm = clip_grads(grads, grad_acc, 1.0)
|
|
||||||
optim.fstep(grads, grad_norm)
|
|
||||||
scheduler.step()
|
scheduler.step()
|
||||||
|
|
||||||
for g in grads: g.assign(0)
|
loss_cpu = loss.flatten().float().to("CPU")
|
||||||
|
|
||||||
lr_cpu = optim.lr.float().to("CPU")
|
lr_cpu = optim.lr.float().to("CPU")
|
||||||
grad_norm_cpu = grad_norm.float().to("CPU")
|
grad_norm_cpu = grad_norm.float().to("CPU")
|
||||||
Tensor.realize(lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales)
|
Tensor.realize(loss_cpu, lr_cpu, grad_norm_cpu, *grads, *fp8_inv_scales)
|
||||||
|
|
||||||
return lr_cpu, grad_norm_cpu
|
return loss_cpu, lr_cpu, grad_norm_cpu
|
||||||
|
|
||||||
@TinyJit
|
@TinyJit
|
||||||
@Context(TRAINING=0)
|
@Context(TRAINING=0)
|
||||||
@@ -1843,30 +1843,20 @@ def train_gptoss():
|
|||||||
profile_marker(f"train @ {i}")
|
profile_marker(f"train @ {i}")
|
||||||
st = time.perf_counter()
|
st = time.perf_counter()
|
||||||
|
|
||||||
stopped = False
|
ist = time.perf_counter()
|
||||||
losses, data_time, dev_time = [], 0, 0
|
|
||||||
for _ in range(grad_acc if i >= 2 else 1):
|
|
||||||
ist = time.perf_counter()
|
|
||||||
try: tokens = next(train_iter)
|
|
||||||
except StopIteration:
|
|
||||||
stopped = True
|
|
||||||
break
|
|
||||||
mst = time.perf_counter()
|
|
||||||
data_time += mst - ist
|
|
||||||
losses.append(minibatch(tokens).item())
|
|
||||||
dev_time += time.perf_counter() - mst
|
|
||||||
if stopped: break
|
|
||||||
|
|
||||||
gt = time.perf_counter()
|
try: tokens = next(train_iter)
|
||||||
ret = optim_step()
|
except StopIteration: break
|
||||||
lr, grad_norm = ret[0].item(), ret[1].item()
|
mst = time.perf_counter()
|
||||||
|
data_time = mst - ist
|
||||||
|
|
||||||
|
ret = step(tokens)
|
||||||
|
dev_time = time.perf_counter() - mst
|
||||||
|
|
||||||
|
loss, lr, grad_norm = ret[0].item(), ret[1].item(), ret[2].item()
|
||||||
et = time.perf_counter()
|
et = time.perf_counter()
|
||||||
|
|
||||||
loss = sum(losses) / len(losses)
|
|
||||||
optim_time = et - gt
|
|
||||||
dev_time += optim_time
|
|
||||||
step_time = et - st
|
step_time = et - st
|
||||||
gbs_time = gt - st
|
|
||||||
if BENCHMARK: step_times.append(step_time)
|
if BENCHMARK: step_times.append(step_time)
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
@@ -1876,7 +1866,7 @@ def train_gptoss():
|
|||||||
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
gflops = GlobalCounters.global_ops / 1e9 / dev_time
|
||||||
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * 4.6e15)) * 100
|
mfu = ((6 * num_params * SEQLEN * GBS) / (dev_time * device_count * 4.6e15)) * 100
|
||||||
tqdm.write(
|
tqdm.write(
|
||||||
f"{i:5} {step_time:.3f} s step, {gbs_time:.3f} s gbs, {optim_time:.3f} s optim, {data_time:.3f} s data, {loss:.4f} loss, " \
|
f"{i:5} {step_time:.3f} s step, {dev_time:.3f} s dev, {data_time:.3f} s data, {loss:.4f} loss, " \
|
||||||
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
f"{lr:.12f} LR, {grad_norm:.6f} grad_norm, {mem_gb:.2f} GB used, {gflops:9.2f} GFLOPS, {mfu:5.2f}% MFU")
|
||||||
if DEBUG >= 1: tqdm.write(" mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
if DEBUG >= 1: tqdm.write(" mem per device: " + ', '.join(f"{dev}: {mem/1e9:.2f} GB" for dev, mem in sorted(GlobalCounters.mem_used_per_device.items())))
|
||||||
|
|
||||||
@@ -1886,8 +1876,6 @@ def train_gptoss():
|
|||||||
"train/lr": lr,
|
"train/lr": lr,
|
||||||
"train/grad_norm": grad_norm,
|
"train/grad_norm": grad_norm,
|
||||||
"train/step_time": step_time,
|
"train/step_time": step_time,
|
||||||
"train/gbs_time": gbs_time,
|
|
||||||
"train/optim_time": optim_time,
|
|
||||||
"train/dev_time": dev_time,
|
"train/dev_time": dev_time,
|
||||||
"train/data_time": data_time,
|
"train/data_time": data_time,
|
||||||
"train/mem": mem_gb,
|
"train/mem": mem_gb,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from tinygrad.helpers import Timing, colored, GlobalCounters, profile_marker
|
|||||||
from tinygrad.uop.ops import Ops, UOp
|
from tinygrad.uop.ops import Ops, UOp
|
||||||
from extra.models.llama import apply_rotary_emb
|
from extra.models.llama import apply_rotary_emb
|
||||||
from extra.llama_kernels.rmsnorm import rmsnorm
|
from extra.llama_kernels.rmsnorm import rmsnorm
|
||||||
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8
|
from extra.gemm.cdna_asm_gemm import _mx_block_scale, _mx_block_scale_3d, quantize_mxfp8, asm_gemm, can_use_asm_gemm
|
||||||
from extra.gemm.moe_gemm import grouped_mx_gemm
|
from extra.gemm.moe_gemm import grouped_mx_gemm
|
||||||
from extra.gemm.moe_routing import route, dispatch, combine
|
from extra.gemm.moe_routing import route, dispatch, combine
|
||||||
|
|
||||||
@@ -61,7 +61,25 @@ def dequant_weight(w_q:Tensor, w_scale:Tensor) -> Tensor:
|
|||||||
call = UOp.maketuple(fxn.uop).call(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd)
|
call = UOp.maketuple(fxn.uop).call(w_q.uop, w_scale.uop, grad_fxn=_dequant_bwd)
|
||||||
return Tensor(call.gettuple(0))
|
return Tensor(call.gettuple(0))
|
||||||
|
|
||||||
def matmul_mx(x:Tensor, w_q:Tensor, w_scale:Tensor) -> Tensor:
|
def matmul_mx(x:Tensor|tuple[Tensor, Tensor], w_q:Tensor, w_scale:Tensor) -> Tensor:
|
||||||
|
if isinstance(x, tuple):
|
||||||
|
assert ASM_GEMM, "pre-quantized MXFP8 input requires ASM_GEMM"
|
||||||
|
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
||||||
|
x_q, x_e8 = x
|
||||||
|
l_shape, padded = x_q.shape[:-1], x_q.shape[-1]
|
||||||
|
x_q, x_e8 = x_q.reshape(-1, padded), x_e8.reshape(-1, padded // 32)
|
||||||
|
K, N = w_q.shape[1], w_q.shape[0]
|
||||||
|
assert padded >= K and (padded - K) % 32 == 0 and x_e8.shape[-1] == padded // 32
|
||||||
|
wq, ws = w_q, w_scale
|
||||||
|
if (pad := padded - K):
|
||||||
|
wq = wq.pad(((0, 0), (0, pad)))
|
||||||
|
ws = ws.pad(((0, 0), (0, pad // 32)), value=127).cast(dtypes.uint8)
|
||||||
|
if (npad := (-N) % 256):
|
||||||
|
wq = wq.pad(((0, npad), (0, 0)))
|
||||||
|
ws = ws.pad(((0, npad), (0, 0)), value=127).cast(dtypes.uint8)
|
||||||
|
assert can_use_asm_gemm(x_q, wq.T)
|
||||||
|
out = asm_gemm(x_q, wq.T, mx=True, mx_scales=(mx_pack(x_e8), x_e8, mx_pack(ws), ws), mx_w_stored=True)
|
||||||
|
return (out[:, :N] if npad else out).reshape(*l_shape, N).cast(dtypes.bfloat16)
|
||||||
l_shape = x.shape[:-1]
|
l_shape = x.shape[:-1]
|
||||||
if ASM_GEMM:
|
if ASM_GEMM:
|
||||||
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
from extra.gemm.cdna_asm_gemm import asm_gemm, can_use_asm_gemm, mx_pack
|
||||||
@@ -146,6 +164,7 @@ class GPTOSS:
|
|||||||
return w_q, w_e8.is_param_(False)
|
return w_q, w_e8.is_param_(False)
|
||||||
if moe:
|
if moe:
|
||||||
qs = [_one(*shape[1:]) for _ in range(shape[0])]
|
qs = [_one(*shape[1:]) for _ in range(shape[0])]
|
||||||
|
for q in qs: q[0]._zero2 = True # grad arrives sharded on the expert axis under ZeRO-2 (moe_gemm)
|
||||||
return [q[0] for q in qs], [q[1] for q in qs]
|
return [q[0] for q in qs], [q[1] for q in qs]
|
||||||
return _one(*shape)
|
return _one(*shape)
|
||||||
|
|
||||||
@@ -174,20 +193,30 @@ class GPTOSS:
|
|||||||
def attention(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, sliding:bool, *, attention_norm:Tensor, wqkv:Tensor,
|
def attention(self, x:Tensor, freqs_cis:Tensor, mask:Tensor, sliding:bool, *, attention_norm:Tensor, wqkv:Tensor,
|
||||||
wqkv_scale:Tensor, wqkv_bias:Tensor, wo:Tensor, wo_scale:Tensor, wo_bias:Tensor, sinks:Tensor):
|
wqkv_scale:Tensor, wqkv_bias:Tensor, wo:Tensor, wo_scale:Tensor, wo_bias:Tensor, sinks:Tensor):
|
||||||
bsz, seqlen, _ = x.shape
|
bsz, seqlen, _ = x.shape
|
||||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
|
||||||
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
|
if getenv("FUSED_RMSNORM_MUL", 0):
|
||||||
|
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul
|
||||||
|
x_normed, rrms = rmsnorm_mul(x, attention_norm, self.norm_eps)
|
||||||
|
norm_saves = [x_normed, rrms]
|
||||||
|
else:
|
||||||
|
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||||
|
qkv = matmul_mx(x_normed * attention_norm, wqkv, wqkv_scale) + wqkv_bias
|
||||||
|
norm_saves = [x_normed, rrms]
|
||||||
|
|
||||||
qkv = qkv.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep + 2, self.head_dim)
|
qkv = qkv.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep + 2, self.head_dim)
|
||||||
xq = qkv[:, :, :, :self.n_rep].reshape(bsz, seqlen, self.n_heads, self.head_dim)
|
xq = qkv[:, :, :, :self.n_rep].reshape(bsz, seqlen, self.n_heads, self.head_dim)
|
||||||
xk, xv = qkv[:, :, :, self.n_rep], qkv[:, :, :, self.n_rep + 1]
|
xk, xv = qkv[:, :, :, self.n_rep], qkv[:, :, :, self.n_rep + 1]
|
||||||
xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
|
xq, xk = apply_rotary_emb(xq, xk, freqs_cis)
|
||||||
xq, xk, xv = xq.cast(dtypes.bfloat16), xk.cast(dtypes.bfloat16), xv.cast(dtypes.bfloat16) # (B,N,H,D)/(B,N,KV,D)
|
xq, xk, xv = xq.cast(dtypes.bfloat16), xk.cast(dtypes.bfloat16), xv.cast(dtypes.bfloat16) # (B,N,H,D)/(B,N,KV,D)
|
||||||
|
|
||||||
if sliding:
|
fa_saves = []
|
||||||
attn = self._sliding_attention(xq, xk, xv, sinks)
|
if getenv("HK_FLASH_ATTENTION"):
|
||||||
elif getenv("HK_FLASH_ATTENTION"):
|
|
||||||
from extra.thunder.amd.fa import flash_attention
|
from extra.thunder.amd.fa import flash_attention
|
||||||
attn, *_ = flash_attention(xq, xk, xv, is_causal=True, write_flat=True, sinks=sinks)
|
attn, _, l_vec = flash_attention(xq, xk, xv, is_causal=True, write_flat=True, sinks=sinks, window=self.sliding_window if sliding else 0)
|
||||||
attn = attn.reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
attn = attn.reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||||
|
fa_saves = [xq, xk, xv, l_vec]
|
||||||
|
elif sliding:
|
||||||
|
attn = self._sliding_attention(xq, xk, xv, sinks)
|
||||||
else:
|
else:
|
||||||
xqm = xq.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep, self.head_dim).permute(0, 2, 3, 1, 4)
|
xqm = xq.reshape(bsz, seqlen, self.n_kv_heads, self.n_rep, self.head_dim).permute(0, 2, 3, 1, 4)
|
||||||
xkm, xvm = xk.permute(0, 2, 1, 3).unsqueeze(2), xv.permute(0, 2, 1, 3).unsqueeze(2)
|
xkm, xvm = xk.permute(0, 2, 1, 3).unsqueeze(2), xv.permute(0, 2, 1, 3).unsqueeze(2)
|
||||||
@@ -199,13 +228,19 @@ class GPTOSS:
|
|||||||
attn = (w @ xvm).permute(0, 3, 1, 2, 4).reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
attn = (w @ xvm).permute(0, 3, 1, 2, 4).reshape(bsz, seqlen, self.n_heads * self.head_dim)
|
||||||
|
|
||||||
out = matmul_mx(attn, wo, wo_scale) + wo_bias
|
out = matmul_mx(attn, wo, wo_scale) + wo_bias
|
||||||
return out, [x_normed, rrms, attn]
|
return out, [attn] + norm_saves + fa_saves
|
||||||
|
|
||||||
def feed_forward(self, x:Tensor, *, ffn_norm:Tensor, gate:Tensor, gate_bias:Tensor,
|
def feed_forward(self, x:Tensor, *, ffn_norm:Tensor, gate:Tensor, gate_bias:Tensor,
|
||||||
w_gate_up:Tensor, w_gate_up_scale:Tensor, w_gate_up_bias:Tensor,
|
w_gate_up:Tensor, w_gate_up_scale:Tensor, w_gate_up_bias:Tensor,
|
||||||
w_down:Tensor, w_down_scale:Tensor, w_down_bias:Tensor):
|
w_down:Tensor, w_down_scale:Tensor, w_down_bias:Tensor):
|
||||||
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
if getenv("FUSED_RMSNORM_MUL", 0):
|
||||||
inp = x_normed * ffn_norm
|
from extra.gptoss_kernels.rmsnorm import rmsnorm_mul
|
||||||
|
x_normed, rrms = rmsnorm_mul(x, ffn_norm, self.norm_eps)
|
||||||
|
inp = x_normed
|
||||||
|
else:
|
||||||
|
x_normed, rrms = rmsnorm(x, self.norm_eps)
|
||||||
|
inp = x_normed * ffn_norm
|
||||||
|
|
||||||
logits = inp.float() @ gate.float().T + gate_bias.float()
|
logits = inp.float() @ gate.float().T + gate_bias.float()
|
||||||
dim, inter = self.dim, self.intermediate_size
|
dim, inter = self.dim, self.intermediate_size
|
||||||
|
|
||||||
@@ -220,6 +255,7 @@ class GPTOSS:
|
|||||||
z = grouped_mx_gemm(_pad_cols(y.cast(dtypes.bfloat16)), (w_down, w_down_scale), r.off)[:, :dim] \
|
z = grouped_mx_gemm(_pad_cols(y.cast(dtypes.bfloat16)), (w_down, w_down_scale), r.off)[:, :dim] \
|
||||||
+ (onehot @ w_down_bias.float()).cast(dtypes.bfloat16)
|
+ (onehot @ w_down_bias.float()).cast(dtypes.bfloat16)
|
||||||
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
|
out = combine(z, r, inp.shape[0], self.experts_per_tok).reshape(bsz, seqlen, dim)
|
||||||
|
return out, [x_normed, rrms, xg, h, y, z, r.weights, r.dest_row, r.off]
|
||||||
else:
|
else:
|
||||||
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
|
thresh = logits.topk(self.experts_per_tok)[0][..., -1:]
|
||||||
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
|
weights = (logits >= thresh).where(logits, -float("inf")).softmax(-1)
|
||||||
@@ -263,7 +299,11 @@ class GPTOSS:
|
|||||||
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
|
w_down=self.w_down[i], w_down_scale=self.w_down_scale[i], w_down_bias=self.w_down_bias[i])
|
||||||
h, *_ = self.run_layer(h, freqs_cis, mask_full, i % 2 == 0, attn_kwargs, ffn_kwargs, save=save)
|
h, *_ = self.run_layer(h, freqs_cis, mask_full, i % 2 == 0, attn_kwargs, ffn_kwargs, save=save)
|
||||||
|
|
||||||
logits = self.norm(h) @ self.output.T
|
h_normed = self.norm(h)
|
||||||
|
pad = (-self.dim) % 256
|
||||||
|
h_padded, w_padded = h_normed.pad((None, None, (0, pad))), self.output.pad(((0, 0), (0, pad)))
|
||||||
|
if ASM_GEMM and can_use_asm_gemm(h_padded, w_padded.T): logits = asm_gemm(h_padded, w_padded.T)
|
||||||
|
else: logits = h_normed @ self.output.T
|
||||||
return logits
|
return logits
|
||||||
|
|
||||||
def _get_pads(uop:UOp) -> list[UOp]:
|
def _get_pads(uop:UOp) -> list[UOp]:
|
||||||
@@ -274,14 +314,14 @@ def apply_grad(grad_buf:Tensor, new_grad:UOp):
|
|||||||
pads = _get_pads(new_grad)
|
pads = _get_pads(new_grad)
|
||||||
if len(pads) <= 1:
|
if len(pads) <= 1:
|
||||||
new_grad = new_grad.cast(grad_buf.dtype)
|
new_grad = new_grad.cast(grad_buf.dtype)
|
||||||
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(grad_buf.uop + new_grad))
|
grad_buf.uop = grad_buf.uop.after(grad_buf.uop.store(new_grad))
|
||||||
return
|
return
|
||||||
cur = grad_buf.uop
|
cur = grad_buf.uop
|
||||||
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True):
|
for pad in sorted(pads, key=lambda p: p.marg[0][0] if p.op == Ops.PAD else 0, reverse=True):
|
||||||
if pad.op == Ops.PAD:
|
if pad.op == Ops.PAD:
|
||||||
grad_shrink = tuple([(p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg)])
|
grad_shrink = tuple((p[0], s+p[0]) for s,p in zip(pad.src[0].shape, pad.marg))
|
||||||
buf_slice = cur.shrink(grad_shrink)
|
buf_slice = cur.shrink(grad_shrink)
|
||||||
cur = cur.after(buf_slice.store(buf_slice + pad.src[0].cast(cur.dtype)))
|
cur = cur.after(buf_slice.store(pad.src[0].cast(cur.dtype)))
|
||||||
else:
|
else:
|
||||||
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
|
cur = cur.after(cur.store(cur + pad.cast(cur.dtype)))
|
||||||
grad_buf.uop = cur
|
grad_buf.uop = cur
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def stochastic_round_bf16(x:Tensor) -> Tensor:
|
|||||||
bits = x.bitcast(dtypes.uint32)
|
bits = x.bitcast(dtypes.uint32)
|
||||||
if isinstance(x.device, tuple):
|
if isinstance(x.device, tuple):
|
||||||
shape = x.uop.shard_shape if x.uop.axis is not None else x.shape
|
shape = x.uop.shard_shape if x.uop.axis is not None else x.shape
|
||||||
noise = Tensor(UOp(Ops.MSTACK, dtypes.default_float, tuple(Tensor.rand(*shape, device=d).uop for d in x.device)))
|
noise = Tensor(UOp(Ops.MSTACK, src=tuple(Tensor.rand(*shape, device=d).uop for d in x.device)))
|
||||||
else:
|
else:
|
||||||
noise = x.rand_like()
|
noise = x.rand_like()
|
||||||
noise = (noise * 0xFFFF).cast(dtypes.uint32)
|
noise = (noise * 0xFFFF).cast(dtypes.uint32)
|
||||||
@@ -27,6 +27,11 @@ def clip_grads(grads:list[Tensor], grad_acc, clip_norm) -> Tensor:
|
|||||||
for g in grads: g.assign((g * (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)).cast(g.dtype))
|
for g in grads: g.assign((g * (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)).cast(g.dtype))
|
||||||
return total_norm
|
return total_norm
|
||||||
|
|
||||||
|
def fclip_grads(grads:list[Tensor], clip_norm) -> Tensor:
|
||||||
|
total_norm = Tensor.stack(*[g.float().square().sum() for g in grads]).sum().sqrt().contiguous()
|
||||||
|
scale = (clip_norm / (total_norm + 1e-6)).clamp(max_=1.0)
|
||||||
|
return [(g * scale).cast(g.dtype) for g in grads], total_norm
|
||||||
|
|
||||||
class GradAccClipAdamW(Optimizer):
|
class GradAccClipAdamW(Optimizer):
|
||||||
def __init__(self, params:list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-6, weight_decay=0.0, grad_acc=1, clip_norm=1.0, device=None, fused=FUSE_OPTIM):
|
def __init__(self, params:list[Tensor], lr=0.001, b1=0.9, b2=0.999, eps=1e-6, weight_decay=0.0, grad_acc=1, clip_norm=1.0, device=None, fused=FUSE_OPTIM):
|
||||||
super().__init__(params, lr, device, fused)
|
super().__init__(params, lr, device, fused)
|
||||||
|
|||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from examples.mlperf.dataloader import get_llama3_dataset
|
||||||
|
from tinygrad.helpers import getenv
|
||||||
|
|
||||||
|
BASEDIR = Path(getenv("BASEDIR", "/raid/datasets/c4-8b/"))
|
||||||
|
SAMPLES = getenv("SAMPLES", 1_200_000 * 32)
|
||||||
|
EVAL_SAMPLES = getenv("EVAL_SAMPLES", 1024)
|
||||||
|
SEQLEN = getenv("SEQLEN", 8192)
|
||||||
|
DATA_SEED = getenv("DATA_SEED", 5760)
|
||||||
|
|
||||||
|
get_llama3_dataset(SAMPLES, SEQLEN, BASEDIR, seed=DATA_SEED, val=False, small=True)
|
||||||
|
get_llama3_dataset(EVAL_SAMPLES, SEQLEN, BASEDIR, seed=0, val=True, small=True)
|
||||||
+3
-3
@@ -26,7 +26,7 @@ export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
|
|||||||
export SPLIT_W13=${SPLIT_W13:-0}
|
export SPLIT_W13=${SPLIT_W13:-0}
|
||||||
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
||||||
|
|
||||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||||
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
||||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export BASEDIR="/raid/datasets/c4-8b/"
|
|||||||
export SMALL=1
|
export SMALL=1
|
||||||
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
||||||
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
||||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=4096 MAX_STEPS=1200000
|
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=2048 MAX_STEPS=1200000
|
||||||
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
||||||
export SAMPLES=$((MAX_STEPS * GBS))
|
export SAMPLES=$((MAX_STEPS * GBS))
|
||||||
export SEQLEN=${SEQLEN:-8192}
|
export SEQLEN=${SEQLEN:-8192}
|
||||||
@@ -44,7 +44,7 @@ export SEED=${SEED:-5760}
|
|||||||
export DATA_SEED=${DATA_SEED:-5760}
|
export DATA_SEED=${DATA_SEED:-5760}
|
||||||
|
|
||||||
export JITBEAM=${JITBEAM:-3}
|
export JITBEAM=${JITBEAM:-3}
|
||||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||||
|
|
||||||
export FAKEDATA=${FAKEDATA:-1} BENCHMARK=${BENCHMARK:-10}
|
export FAKEDATA=${FAKEDATA:-1} BENCHMARK=${BENCHMARK:-10}
|
||||||
if [ -z "$FULL_LAYERS" ]; then
|
if [ -z "$FULL_LAYERS" ]; then
|
||||||
|
|||||||
+5
-5
@@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
export PYTHONPATH="."
|
export PYTHONPATH="."
|
||||||
export PATH="/opt/rocm-7.1.1/bin:$PATH"
|
export ROCM_PATH=${ROCM_PATH:-/opt/rocm-7.1.1}
|
||||||
export ROCM_PATH="/opt/rocm-7.1.1"
|
export PATH="$ROCM_PATH/bin:$PATH"
|
||||||
export DEV=${DEV:-AMD}
|
export DEV=${DEV:-AMD}
|
||||||
export CHECK_OOB=0
|
export CHECK_OOB=0
|
||||||
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
|
export REWRITE_STACK_LIMIT=5000000 HCQDEV_WAIT_TIMEOUT_MS=240000
|
||||||
@@ -26,7 +26,7 @@ export FUSED_SILU_W13=${FUSED_SILU_W13:-1}
|
|||||||
export SPLIT_W13=${SPLIT_W13:-0}
|
export SPLIT_W13=${SPLIT_W13:-0}
|
||||||
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
export OFFLOAD_OPTIM=${OFFLOAD_OPTIM:-0}
|
||||||
|
|
||||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||||
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
export DP=${DP:-8} MP=${MP:-1} BS=${BS:-16} EVAL_BS=${EVAL_BS:-8} GRADIENT_ACC_STEPS=${GRADIENT_ACC_STEPS:-2}
|
||||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export BASEDIR="/raid/datasets/c4-8b/"
|
|||||||
export SMALL=1
|
export SMALL=1
|
||||||
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
export LLAMA3_SIZE=${LLAMA3_SIZE:-"8B"}
|
||||||
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
export EVAL_TARGET=3.3 EVAL_FREQ=12288
|
||||||
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=4096 MAX_STEPS=1200000
|
export LR="1e-3" END_LR="1e-4" WARMUP_SAMPLES=2048 MAX_STEPS=1200000
|
||||||
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
export WARMUP_STEPS=$((WARMUP_SAMPLES / GBS))
|
||||||
export SAMPLES=$((MAX_STEPS * GBS))
|
export SAMPLES=$((MAX_STEPS * GBS))
|
||||||
export SEQLEN=${SEQLEN:-8192}
|
export SEQLEN=${SEQLEN:-8192}
|
||||||
@@ -44,6 +44,6 @@ export SEED=${SEED:-$RANDOM}
|
|||||||
export DATA_SEED=${DATA_SEED:-5760}
|
export DATA_SEED=${DATA_SEED:-5760}
|
||||||
|
|
||||||
export JITBEAM=${JITBEAM:-3}
|
export JITBEAM=${JITBEAM:-3}
|
||||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||||
|
|
||||||
python3 examples/mlperf/model_train.py
|
python3 examples/mlperf/model_train.py
|
||||||
|
|||||||
+3
-3
@@ -17,7 +17,7 @@ export USE_ATOMICS=1
|
|||||||
export ASM_GEMM=1
|
export ASM_GEMM=1
|
||||||
export WQKV=1
|
export WQKV=1
|
||||||
export MASTER_WEIGHTS=1
|
export MASTER_WEIGHTS=1
|
||||||
export FP8=1
|
export MXFP4=1
|
||||||
export ALLREDUCE_CAST=1
|
export ALLREDUCE_CAST=1
|
||||||
export FAST_CE=1
|
export FAST_CE=1
|
||||||
export FUSED_INPUT_QUANTIZE=1
|
export FUSED_INPUT_QUANTIZE=1
|
||||||
@@ -26,7 +26,7 @@ export FUSED_ADD_NORM_MUL_QUANTIZE=1
|
|||||||
export FUSED_SILU_W13=1
|
export FUSED_SILU_W13=1
|
||||||
export SPLIT_W13=0
|
export SPLIT_W13=0
|
||||||
|
|
||||||
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="bfloat16"
|
export DEFAULT_FLOAT="bfloat16" OPTIM_DTYPE="float32"
|
||||||
export DP=8 MP=1 BS=16 EVAL_BS=8 GRADIENT_ACC_STEPS=2
|
export DP=8 MP=1 BS=16 EVAL_BS=8 GRADIENT_ACC_STEPS=2
|
||||||
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
export GBS=$((BS * GRADIENT_ACC_STEPS))
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ export SEED=$RANDOM
|
|||||||
export DATA_SEED=$SEED
|
export DATA_SEED=$SEED
|
||||||
|
|
||||||
export JITBEAM=3
|
export JITBEAM=3
|
||||||
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=1
|
export BEAM_UOPS_MAX=6000 BEAM_UPCAST_MAX=256 BEAM_LOCAL_MAX=1024 BEAM_MIN_PROGRESS=5 BEAM_PADTO=0
|
||||||
|
|
||||||
export LOGMLPERF=1
|
export LOGMLPERF=1
|
||||||
|
|
||||||
|
|||||||
+1
@@ -11,6 +11,7 @@ export DEVICE_IN_FUNCTION_BUG=1
|
|||||||
export DEBUG=${DEBUG:-2}
|
export DEBUG=${DEBUG:-2}
|
||||||
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
||||||
export ASM_GEMM=${ASM_GEMM:-1}
|
export ASM_GEMM=${ASM_GEMM:-1}
|
||||||
|
export GROUPED_MOE=${GROUPED_MOE:-1}
|
||||||
export ALL2ALL=${ALL2ALL:-1}
|
export ALL2ALL=${ALL2ALL:-1}
|
||||||
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
||||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||||
|
|||||||
+1
@@ -11,6 +11,7 @@ export DEVICE_IN_FUNCTION_BUG=1
|
|||||||
export DEBUG=${DEBUG:-0}
|
export DEBUG=${DEBUG:-0}
|
||||||
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
export HK_FLASH_ATTENTION=${HK_FLASH_ATTENTION:-1}
|
||||||
export ASM_GEMM=${ASM_GEMM:-1}
|
export ASM_GEMM=${ASM_GEMM:-1}
|
||||||
|
export GROUPED_MOE=${GROUPED_MOE:-1}
|
||||||
export ALL2ALL=${ALL2ALL:-1}
|
export ALL2ALL=${ALL2ALL:-1}
|
||||||
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
export LATE_ALLREDUCE=${LATE_ALLREDUCE:-0}
|
||||||
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
export ALLREDUCE_CAST=${ALLREDUCE_CAST:-1}
|
||||||
|
|||||||
@@ -107,14 +107,21 @@ def compile(onnx_file):
|
|||||||
return inputs, test_val
|
return inputs, test_val
|
||||||
|
|
||||||
def test_vs_compile(run, inputs, test_val=None):
|
def test_vs_compile(run, inputs, test_val=None):
|
||||||
|
if (log:=bool(getenv("BENCHMARK_LOG", ""))): from extra.bench_log import WallTimeEvent, BenchEvent
|
||||||
|
|
||||||
# run 20 times
|
# run 20 times
|
||||||
step_times = []
|
step_times = []
|
||||||
for _ in range(20):
|
for _ in range(20):
|
||||||
st = time.perf_counter()
|
st = time.perf_counter()
|
||||||
out = run(**inputs)
|
if log:
|
||||||
mt = time.perf_counter()
|
with WallTimeEvent(BenchEvent.STEP):
|
||||||
val = out.numpy()
|
out = run(**inputs)
|
||||||
|
mt = time.perf_counter()
|
||||||
|
val = out.numpy()
|
||||||
|
else:
|
||||||
|
out = run(**inputs)
|
||||||
|
mt = time.perf_counter()
|
||||||
|
val = out.numpy()
|
||||||
et = time.perf_counter()
|
et = time.perf_counter()
|
||||||
step_times.append((et-st)*1e3)
|
step_times.append((et-st)*1e3)
|
||||||
print(f"enqueue {(mt-st)*1e3:6.2f} ms -- total run {step_times[-1]:6.2f} ms")
|
print(f"enqueue {(mt-st)*1e3:6.2f} ms -- total run {step_times[-1]:6.2f} ms")
|
||||||
@@ -160,12 +167,6 @@ def test_vs_onnx(new_inputs, test_val, onnx_file, tol):
|
|||||||
print("test vs onnx passed")
|
print("test vs onnx passed")
|
||||||
return timings
|
return timings
|
||||||
|
|
||||||
def bench(run, inputs):
|
|
||||||
from extra.bench_log import WallTimeEvent, BenchEvent
|
|
||||||
for _ in range(10):
|
|
||||||
with WallTimeEvent(BenchEvent.STEP):
|
|
||||||
run(**inputs).numpy()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if getenv("RUN_PICKLE"):
|
if getenv("RUN_PICKLE"):
|
||||||
with open(OUTPUT, "rb") as f: pickle_loaded = load_pickle(f)
|
with open(OUTPUT, "rb") as f: pickle_loaded = load_pickle(f)
|
||||||
@@ -181,6 +182,3 @@ if __name__ == "__main__":
|
|||||||
test_vs_compile(pickle_loaded, inputs, outputs)
|
test_vs_compile(pickle_loaded, inputs, outputs)
|
||||||
if getenv("SELFTEST"):
|
if getenv("SELFTEST"):
|
||||||
test_vs_onnx(inputs, outputs, onnx_file, 1e-4)
|
test_vs_onnx(inputs, outputs, onnx_file, 1e-4)
|
||||||
|
|
||||||
if getenv("BENCHMARK_LOG", ""):
|
|
||||||
bench(pickle_loaded, inputs)
|
|
||||||
|
|||||||
@@ -84,7 +84,8 @@ class AMSMI(AMDev):
|
|||||||
with open(f"/sys/bus/pci/devices/{self.pcibus}/power_state", "r") as f: return f.read().strip().rstrip()
|
with open(f"/sys/bus/pci/devices/{self.pcibus}/power_state", "r") as f: return f.read().strip().rstrip()
|
||||||
|
|
||||||
class SMICtx:
|
class SMICtx:
|
||||||
def __init__(self):
|
def __init__(self, dev_filter=None):
|
||||||
|
self.dev_filter = dev_filter
|
||||||
self.devs = []
|
self.devs = []
|
||||||
self.opened_pcidevs = []
|
self.opened_pcidevs = []
|
||||||
self.opened_pci_resources = {}
|
self.opened_pci_resources = {}
|
||||||
@@ -135,6 +136,7 @@ class SMICtx:
|
|||||||
pattern = os.path.join('/tmp', 'am_*.lock')
|
pattern = os.path.join('/tmp', 'am_*.lock')
|
||||||
for d in [f[8:-5] for f in glob.glob(pattern)]:
|
for d in [f[8:-5] for f in glob.glob(pattern)]:
|
||||||
if d.startswith("usb"): continue
|
if d.startswith("usb"): continue
|
||||||
|
if self.dev_filter is not None and d != self.dev_filter: continue
|
||||||
if d not in self.opened_pcidevs:
|
if d not in self.opened_pcidevs:
|
||||||
self._open_am_device(d)
|
self._open_am_device(d)
|
||||||
|
|
||||||
@@ -406,7 +408,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if not args.list: os.system('clear')
|
if not args.list: os.system('clear')
|
||||||
smi_ctx = SMICtx()
|
smi_ctx = SMICtx(args.dev)
|
||||||
while True:
|
while True:
|
||||||
smi_ctx.rescan_devs()
|
smi_ctx.rescan_devs()
|
||||||
smi_ctx.draw(args.list)
|
smi_ctx.draw(args.list)
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class WallTimeEvent:
|
|||||||
return self
|
return self
|
||||||
def __exit__(self, *_):
|
def __exit__(self, *_):
|
||||||
self.time = time.monotonic() - self.start
|
self.time = time.monotonic() - self.start
|
||||||
_events[self.event]["wall"].append(self.time)
|
_events[self.event]["wall"].append((self.time, BENCHMARK_LOG.value))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class KernelTimeEvent:
|
class KernelTimeEvent:
|
||||||
@@ -47,19 +47,19 @@ class KernelTimeEvent:
|
|||||||
self.start = GlobalCounters.time_sum_s
|
self.start = GlobalCounters.time_sum_s
|
||||||
return self
|
return self
|
||||||
def __exit__(self, *_):
|
def __exit__(self, *_):
|
||||||
_events[self.event]["kernel"].append(GlobalCounters.time_sum_s - self.start)
|
_events[self.event]["kernel"].append((GlobalCounters.time_sum_s - self.start, BENCHMARK_LOG.value))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def log_event_instant(event:InstantBenchEvent, value:float):
|
def log_event_instant(event:InstantBenchEvent, value:float):
|
||||||
_events[event].append(value)
|
_events[event].append((value, BENCHMARK_LOG.value))
|
||||||
|
|
||||||
if BENCHMARK_LOG:
|
if BENCHMARK_LOG:
|
||||||
INFLUXDB_HOST = getenv("INFLUXDB_HOST", "")
|
INFLUXDB_HOST = getenv("INFLUXDB_HOST", "")
|
||||||
INFLUXDB_ORG = getenv("INFLUXDB_ORG", "tiny")
|
INFLUXDB_ORG = getenv("INFLUXDB_ORG", "tiny")
|
||||||
INFLUXDB_TOKEN = getenv("INFLUXDB_TOKEN", "")
|
INFLUXDB_TOKEN = getenv("INFLUXDB_TOKEN", "")
|
||||||
|
|
||||||
def _create_point(run_id, i, attempt, ref, commit, name, value, run):
|
def _create_point(run_id, i, attempt, ref, commit, name, value, log_name, run):
|
||||||
point = Point(BENCHMARK_LOG.value).tag("id", run_id).tag("index", i)
|
point = Point(log_name.replace(':', '_').replace('.', '_')).tag("id", run_id).tag("index", i)
|
||||||
point = point.tag("device", Device.DEFAULT)
|
point = point.tag("device", Device.DEFAULT)
|
||||||
point = point.tag("attempt", attempt).tag("ref", ref).tag("commit", commit)
|
point = point.tag("attempt", attempt).tag("ref", ref).tag("commit", commit)
|
||||||
point = point.field(name, value).field("x", run)
|
point = point.field(name, value).field("x", run)
|
||||||
@@ -91,12 +91,12 @@ if BENCHMARK_LOG:
|
|||||||
run_id = str(uuid.uuid4())
|
run_id = str(uuid.uuid4())
|
||||||
if isinstance(event, BenchEvent):
|
if isinstance(event, BenchEvent):
|
||||||
for event_type, values in _events[event].items():
|
for event_type, values in _events[event].items():
|
||||||
for i, value in enumerate(values):
|
for i, (value, log_name) in enumerate(values):
|
||||||
point = _create_point(run_id, i, attempt, ref, commit, f"{event.value}_{event_type}", value, run)
|
point = _create_point(run_id, i, attempt, ref, commit, f"{event.value}_{event_type}", value, log_name, run)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
else:
|
else:
|
||||||
for i, value in enumerate(_events[event]):
|
for i, (value, log_name) in enumerate(_events[event]):
|
||||||
point = _create_point(run_id, i, attempt, ref, commit, event.value, value, run)
|
point = _create_point(run_id, i, attempt, ref, commit, event.value, value, log_name, run)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
|
|
||||||
write_options = WriteOptions(write_type=WriteType.synchronous, retry_interval=5000, max_retries=5, max_retry_delay=30000, exponential_base=2)
|
write_options = WriteOptions(write_type=WriteType.synchronous, retry_interval=5000, max_retries=5, max_retry_delay=30000, exponential_base=2)
|
||||||
|
|||||||
@@ -0,0 +1,238 @@
|
|||||||
|
import ctypes, struct
|
||||||
|
from tinygrad.helpers import ceildiv, getenv, wait_cond, DEBUG
|
||||||
|
from tinygrad.runtime.autogen import bnxt, pci
|
||||||
|
from tinygrad.runtime.support.system import PCIDevice, System, ipv4_to_gid
|
||||||
|
|
||||||
|
BNXT_DEBUG = getenv("BNXT_DEBUG", 0)
|
||||||
|
BNXT_ACCESS, BNXT_INIT_MASK, BNXT_RTR_MASK, BNXT_RTS_MASK = 3, 0xd, 0x41515ad, 0xae005
|
||||||
|
BNXT_CHIMP_COMM, BNXT_CHIMP_COMM_TRIGGER = 0x0, 0x100
|
||||||
|
BNXT_BACKING_STORE = ((0, 2), (1, 0), (2, 2), (3, 0), (4, 2), (5, 0), (6, 0), (14, 2), (15, 0))
|
||||||
|
|
||||||
|
def db_value(xid, typ, index, epoch):
|
||||||
|
return (xid & bnxt.DBC_DBC_XID_MASK | bnxt.DBC_DBC_PATH_ROCE | typ | bnxt.BNXT_QPLIB_DBR_VALID) << 32 | \
|
||||||
|
index & bnxt.DBC_DBC_INDEX_MASK | epoch << bnxt.BNXT_QPLIB_DBR_EPOCH_SHIFT
|
||||||
|
|
||||||
|
def _pbl(dev, paddrs, queue=False):
|
||||||
|
if len(paddrs) == 1: return 0, paddrs[0]
|
||||||
|
values = [p | bnxt.PTU_PTE_VALID for p in paddrs]
|
||||||
|
if queue:
|
||||||
|
values[-1] |= bnxt.PTU_PTE_LAST
|
||||||
|
if len(values) > 1: values[-2] |= bnxt.PTU_PTE_NEXT_TO_LAST
|
||||||
|
table, table_paddrs = dev.pci_dev.alloc_sysmem(ceildiv(len(values), 512) * 0x1000)
|
||||||
|
table[:len(values) * 8] = struct.pack(f"<{len(values)}Q", *values)
|
||||||
|
if len(table_paddrs) == 1: return 1, table_paddrs[0]
|
||||||
|
top, top_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||||
|
top[:len(table_paddrs) * 8] = struct.pack(f"<{len(table_paddrs)}Q", *(p | bnxt.PTU_PTE_VALID for p in table_paddrs))
|
||||||
|
return 2, top_paddrs[0]
|
||||||
|
|
||||||
|
def _queue(dev, stride:int=16, aux=False):
|
||||||
|
mem, paddrs = dev.pci_dev.alloc_sysmem(0x1000 + aux * 0x400)
|
||||||
|
level, base = _pbl(dev, paddrs, queue=True)
|
||||||
|
return {"mem":mem, "paddrs":paddrs, "stride":stride, "prod":0, "cons":0, "level":level, "base":base}
|
||||||
|
|
||||||
|
def _qread(q, i):
|
||||||
|
off = (i & 15) * q["stride"]
|
||||||
|
return q["mem"][off:off + q["stride"]]
|
||||||
|
|
||||||
|
def _qwrite(q, i, data, aux=False):
|
||||||
|
off = 0x1000 + i % 128 * 8 if aux else (i & 15) * q["stride"]
|
||||||
|
q["mem"][off:off + len(data)] = data
|
||||||
|
|
||||||
|
class BNXTDev:
|
||||||
|
def __init__(self, pci_dev:PCIDevice, ip:str=getenv("BNXT_IP", "10.0.0.1")):
|
||||||
|
self.pci_dev, self.devfmt = pci_dev, pci_dev.pcibus
|
||||||
|
self.bar0, self.db = pci_dev.map_bar(0, fmt='I'), pci_dev.map_bar(2, fmt='Q')
|
||||||
|
pci_dev.write_config(pci.PCI_COMMAND, pci_dev.read_config(pci.PCI_COMMAND, 2) | pci.PCI_COMMAND_MASTER, 2)
|
||||||
|
self.resp, self.resp_pa = pci_dev.alloc_sysmem(0x1000)
|
||||||
|
self.seq = 0
|
||||||
|
|
||||||
|
ver = self.hwrm("ver_get")
|
||||||
|
if DEBUG >= 2: print(f"bnxt {self.devfmt}: firmware {ver.hwrm_fw_maj_8b}.{ver.hwrm_fw_min_8b}.{ver.hwrm_fw_bld_8b}")
|
||||||
|
self.hwrm("func_reset", timeout_ms=40000)
|
||||||
|
caps = self.hwrm("func_qcaps", fid=0xffff)
|
||||||
|
self.mac, self.port_id = int.from_bytes(bytes(caps.mac_address), 'big'), caps.port_id
|
||||||
|
self.hwrm("func_drv_rgtr")
|
||||||
|
self.db_off = self.hwrm("func_qcfg", fid=0xffff).legacy_l2_db_size_kb * 1024
|
||||||
|
|
||||||
|
self.setup_backing_store()
|
||||||
|
self._open_rcfw()
|
||||||
|
self._open_l2()
|
||||||
|
self.local_gid = ipv4_to_gid(ip)
|
||||||
|
gids, mac = (ctypes.c_uint32 * 4)(*(int.from_bytes(self.local_gid[i:i + 4], 'big') for i in (12, 8, 4, 0))), self.mac.to_bytes(6, 'big')
|
||||||
|
smac = (ctypes.c_uint16 * 3)(*(int.from_bytes(mac[i:i + 2], 'big') for i in (0, 2, 4)))
|
||||||
|
self.gid_id = self.rcfw("add_gid", gid=gids, src_mac=smac).xid
|
||||||
|
|
||||||
|
if DEBUG >= 2: print(f"bnxt {self.devfmt}: booted mac={self.mac.to_bytes(6, 'big').hex(':')} gid={self.local_gid.hex()}")
|
||||||
|
|
||||||
|
def hwrm(self, name, timeout_ms=10000, **fields):
|
||||||
|
inp, out = getattr(bnxt, f"struct_hwrm_{name}_input"), getattr(bnxt, f"struct_hwrm_{name}_output")
|
||||||
|
opcode = getattr(bnxt, f"HWRM_{name.upper()}")
|
||||||
|
self.seq = (self.seq + 1) & 0xffff
|
||||||
|
data = bytes(inp(req_type=opcode, cmpl_ring=bnxt.BNXT_HWRM_NO_CMPL_RING, seq_id=self.seq, target_id=bnxt.BNXT_HWRM_TARGET,
|
||||||
|
resp_addr=self.resp_pa[0], **fields))
|
||||||
|
self.resp[:] = bytes(len(self.resp))
|
||||||
|
System.memory_barrier()
|
||||||
|
for i, w in enumerate(memoryview(bytearray(data.ljust(bnxt.HWRM_MAX_REQ_LEN, b'\0'))).cast('I')):
|
||||||
|
self.bar0[BNXT_CHIMP_COMM // 4 + i] = w
|
||||||
|
self.bar0[BNXT_CHIMP_COMM_TRIGGER // 4] = 1
|
||||||
|
def hdr(): return bnxt.struct_hwrm_resp_hdr.from_buffer_copy(bytes(self.resp[:8]))
|
||||||
|
wait_cond(lambda: (n := hdr().resp_len) and hdr().seq_id == self.seq and self.resp[n - 1], timeout_ms=timeout_ms, msg=f"HWRM {name}")
|
||||||
|
ret = out.from_buffer_copy(bytes(self.resp[:ctypes.sizeof(out)]))
|
||||||
|
assert ret.error_code == 0, f"HWRM {name}: {ret.error_code}"
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def setup_backing_store(self):
|
||||||
|
counts: dict[int, int] = {}
|
||||||
|
for typ, extra in BNXT_BACKING_STORE:
|
||||||
|
caps = self.hwrm("func_backing_store_qcaps_v2", type=typ)
|
||||||
|
size, splits = caps.entry_size, tuple(getattr(caps, f"split_entry_{j}") for j in range(caps.subtype_valid_cnt))
|
||||||
|
counts[typ] = n = counts[0] if typ == 15 else max(caps.min_num_entries, sum(splits) + extra)
|
||||||
|
# a zero bitmap means the type has a single instance 0
|
||||||
|
for instance in [i for i in range(8) if caps.instance_bit_map >> i & 1] or [0]:
|
||||||
|
mem, paddrs = self.pci_dev.alloc_sysmem(ceildiv(n * size, 0x1000) * 0x1000)
|
||||||
|
if caps.ctx_init_value:
|
||||||
|
for off in range(caps.ctx_init_offset, len(mem), size): mem[off] = caps.ctx_init_value
|
||||||
|
lvl, base = _pbl(self, paddrs)
|
||||||
|
self.hwrm("func_backing_store_cfg_v2", type=typ, instance=instance, entry_size=size, num_entries=n, page_dir=base,
|
||||||
|
page_size_pbl_level=lvl, subtype_valid_cnt=len(splits),
|
||||||
|
flags=bnxt.FUNC_BACKING_STORE_CFG_V2_REQ_FLAGS_BS_CFG_ALL_DONE if typ == 15 else 0,
|
||||||
|
**{f"split_entry_{j}": v for j, v in enumerate(splits)})
|
||||||
|
|
||||||
|
def _open_rcfw(self):
|
||||||
|
self.rcfw_first = True
|
||||||
|
|
||||||
|
self.creq = _queue(self)
|
||||||
|
self.creq_id = self.hwrm("ring_alloc", ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_NQ, page_tbl_addr=self.creq["base"],
|
||||||
|
page_size=12, page_tbl_depth=self.creq["level"], length=16, int_mode=bnxt.RING_ALLOC_REQ_INT_MODE_MSIX).ring_id
|
||||||
|
|
||||||
|
self.cmdq = _queue(self)
|
||||||
|
self.doorbell(self.creq_id, bnxt.DBC_DBC_TYPE_NQ_ARM, 0, 0)
|
||||||
|
init = bnxt.struct_cmdq_init(cmdq_pbl=self.cmdq["base"], creq_ring_id=self.creq_id,
|
||||||
|
cmdq_size_cmdq_lvl=16 << bnxt.CMDQ_INIT_CMDQ_SIZE_SFT)
|
||||||
|
|
||||||
|
System.memory_barrier()
|
||||||
|
for i, w in enumerate(memoryview(bytearray(bytes(init))).cast('I')): self.bar0[bnxt.RCFW_COMM_BASE_OFFSET // 4 + i] = w
|
||||||
|
|
||||||
|
_, p = self.pci_dev.alloc_sysmem(0x1000)
|
||||||
|
self.rcfw("initialize_fw", stat_ctx_id=self.hwrm("stat_ctx_alloc", stats_dma_addr=p[0], stats_dma_length=176).stat_ctx_id,
|
||||||
|
flags=bnxt.CMDQ_INITIALIZE_FW_FLAGS_HW_REQUESTER_RETX_SUPPORTED)
|
||||||
|
|
||||||
|
# RoCE notification ring: never armed or serviced, but CQ and L2 ring allocation require one
|
||||||
|
nq = _queue(self)
|
||||||
|
self.nq_id = self.hwrm("ring_alloc", ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_NQ, page_tbl_addr=nq["base"],
|
||||||
|
page_size=12, page_tbl_depth=nq["level"], length=16, logical_id=1, int_mode=bnxt.RING_ALLOC_REQ_INT_MODE_MSIX).ring_id
|
||||||
|
|
||||||
|
def rcfw(self, name, timeout_ms=20000, **fields):
|
||||||
|
req_t, resp_t = getattr(bnxt, f"struct_cmdq_{name}"), getattr(bnxt, f"struct_creq_{name}_resp")
|
||||||
|
op = getattr(bnxt, f"CMDQ_BASE_OPCODE_{name.upper()}")
|
||||||
|
data = bytes(req_t(opcode=op, cmd_size=(slots := ceildiv(ctypes.sizeof(req_t), 16)), **fields)).ljust(slots * 16, b'\0')
|
||||||
|
for i in range(slots): _qwrite(self.cmdq, self.cmdq["prod"] + i, data[i * 16:(i + 1) * 16])
|
||||||
|
|
||||||
|
self.cmdq["prod"] += slots
|
||||||
|
prod = self.cmdq["prod"] & 0xffff
|
||||||
|
if self.rcfw_first: prod, self.rcfw_first = prod | 1 << bnxt.FIRMWARE_FIRST_FLAG, False
|
||||||
|
|
||||||
|
System.memory_barrier()
|
||||||
|
|
||||||
|
self.bar0[(bnxt.RCFW_COMM_BASE_OFFSET + bnxt.RCFW_PF_VF_COMM_PROD_OFFSET) // 4] = prod
|
||||||
|
self.bar0[(bnxt.RCFW_COMM_BASE_OFFSET + bnxt.RCFW_COMM_TRIG_OFFSET) // 4] = bnxt.RCFW_CMDQ_TRIG_VAL
|
||||||
|
|
||||||
|
def poll():
|
||||||
|
h = bnxt.struct_creq_base.from_buffer_copy(bytes(_qread(self.creq, self.creq["cons"])))
|
||||||
|
return bool(h.v & bnxt.CREQ_BASE_V) != bool((self.creq["cons"] // 16) & 1)
|
||||||
|
wait_cond(poll, timeout_ms=timeout_ms, msg=f"RCFW {name}")
|
||||||
|
|
||||||
|
ret = resp_t.from_buffer_copy(bytes(_qread(self.creq, self.creq["cons"])))
|
||||||
|
self.creq["cons"] += 1
|
||||||
|
|
||||||
|
# NQ_ARM also publishes the CREQ consumer index, which is what frees ring space for the next command
|
||||||
|
self.doorbell(self.creq_id, bnxt.DBC_DBC_TYPE_NQ_ARM, self.creq["cons"] & 15, (self.creq["cons"] // 16) & 1)
|
||||||
|
assert ret.status == 0, f"RCFW {name}: {ret.status}"
|
||||||
|
|
||||||
|
if BNXT_DEBUG >= 1: print(f"bnxt {self.devfmt}: rcfw {name} xid={getattr(ret, 'xid', 0):#x}")
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def doorbell(self, xid, typ, index, epoch):
|
||||||
|
System.memory_barrier()
|
||||||
|
self.db[self.db_off // 8] = db_value(xid, typ, index, epoch)
|
||||||
|
|
||||||
|
# L2 receive path, required for RoCE ingress even though no ethernet receive buffers are posted
|
||||||
|
def _open_l2(self):
|
||||||
|
cq = _queue(self)
|
||||||
|
ci = self.hwrm("ring_alloc", enables=bnxt.RING_ALLOC_REQ_ENABLES_NQ_RING_ID_VALID, ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_L2_CMPL,
|
||||||
|
page_tbl_addr=cq["base"], page_size=12, page_tbl_depth=cq["level"], length=16, nq_ring_id=self.nq_id).ring_id
|
||||||
|
rx = _queue(self)
|
||||||
|
ri = self.hwrm("ring_alloc", enables=bnxt.RING_ALLOC_REQ_ENABLES_NQ_RING_ID_VALID |
|
||||||
|
bnxt.RING_ALLOC_REQ_ENABLES_RX_BUF_SIZE_VALID, ring_type=bnxt.RING_ALLOC_REQ_RING_TYPE_RX, page_tbl_addr=rx["base"],
|
||||||
|
page_size=12, page_tbl_depth=rx["level"], length=16, rx_buf_size=640, nq_ring_id=self.nq_id).ring_id
|
||||||
|
vi = self.hwrm("vnic_alloc").vnic_id
|
||||||
|
self.hwrm("vnic_cfg", enables=bnxt.VNIC_CFG_REQ_ENABLES_MRU | bnxt.VNIC_CFG_REQ_ENABLES_DEFAULT_RX_RING_ID |
|
||||||
|
bnxt.VNIC_CFG_REQ_ENABLES_DEFAULT_CMPL_RING_ID, vnic_id=vi, mru=9018,
|
||||||
|
default_rx_ring_id=ri, default_cmpl_ring_id=ci)
|
||||||
|
self.hwrm("cfa_l2_filter_alloc", flags=bnxt.CFA_L2_FILTER_ALLOC_REQ_FLAGS_PATH_RX,
|
||||||
|
enables=bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_L2_ADDR | bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_L2_ADDR_MASK |
|
||||||
|
bnxt.CFA_L2_FILTER_ALLOC_REQ_ENABLES_DST_ID, l2_addr=tuple(self.mac.to_bytes(6, 'big')), l2_addr_mask=(0xff,) * 6, dst_id=vi)
|
||||||
|
|
||||||
|
def register_mem(self, paddrs:list[int], size:int, log_page_size:int=12) -> int:
|
||||||
|
level, base = _pbl(self, paddrs[:ceildiv(size, 1 << log_page_size)])
|
||||||
|
return self.rcfw("register_mr", flags=bnxt.CMDQ_REGISTER_MR_FLAGS_ALLOC_MR,
|
||||||
|
log2_pg_size_lvl=level << bnxt.CMDQ_REGISTER_MR_LVL_SFT | log_page_size << bnxt.CMDQ_REGISTER_MR_LOG2_PG_SIZE_SFT,
|
||||||
|
access=bnxt.CMDQ_REGISTER_MR_ACCESS_LOCAL_WRITE | bnxt.CMDQ_REGISTER_MR_ACCESS_REMOTE_WRITE,
|
||||||
|
log2_pbl_pg_size=12, pbl=base, va=paddrs[0], mr_size=size).xid
|
||||||
|
|
||||||
|
class BNXTQP:
|
||||||
|
def __init__(self, dev:BNXTDev):
|
||||||
|
self.dev, self.sq_psn, self.msn = dev, 0, 0
|
||||||
|
|
||||||
|
self.cqq = _queue(dev, ctypes.sizeof(bnxt.struct_cq_base))
|
||||||
|
self.cq_id = dev.rcfw("create_cq", cq_size=16, pbl=self.cqq["base"],
|
||||||
|
pg_size_lvl=self.cqq["level"], cq_fco_cnq_id=dev.nq_id).xid
|
||||||
|
|
||||||
|
self.sq = _queue(dev, aux=True)
|
||||||
|
self.qpn = dev.rcfw("create_qp", type=bnxt.CMDQ_CREATE_QP_TYPE_RC,
|
||||||
|
sq_size=16, sq_fwo_sq_sge=1, scq_cid=self.cq_id, rcq_cid=self.cq_id,
|
||||||
|
sq_pbl=self.sq["base"], sq_pg_size_sq_lvl=self.sq["level"]).xid
|
||||||
|
self.qp_op(1, BNXT_INIT_MASK, access=BNXT_ACCESS, pkey=0xffff)
|
||||||
|
|
||||||
|
def qp_op(self, state, mask, network_type=0, **fields):
|
||||||
|
self.dev.rcfw("modify_qp", qp_cid=self.qpn, modify_mask=mask,
|
||||||
|
network_type_en_sqd_async_notify_new_state=state | network_type, **fields)
|
||||||
|
|
||||||
|
def connect(self, qpn:int, gid:bytes, mac:int):
|
||||||
|
network_type = bnxt.CMDQ_MODIFY_QP_NETWORK_TYPE_ROCEV2_IPV4
|
||||||
|
dgid = (ctypes.c_uint32 * 4)(*(int.from_bytes(gid[i:i + 4], 'little') for i in (0, 4, 8, 12)))
|
||||||
|
dmac = (ctypes.c_uint16 * 3)(*(int.from_bytes(mac.to_bytes(6, 'big')[i:i + 2], 'little') for i in (0, 2, 4)))
|
||||||
|
|
||||||
|
self.qp_op(2, BNXT_RTR_MASK, network_type=network_type, qp_type=bnxt.CMDQ_MODIFY_QP_QP_TYPE_RC, access=BNXT_ACCESS,
|
||||||
|
pkey=0xffff, dgid=dgid, sgid_index=self.dev.gid_id, hop_limit=64, dest_mac=dmac,
|
||||||
|
path_mtu_pingpong_push_enable=bnxt.CMDQ_MODIFY_QP_PATH_MTU_MTU_1024, max_dest_rd_atomic=4,
|
||||||
|
dest_qp_id=qpn)
|
||||||
|
self.qp_op(3, BNXT_RTS_MASK, network_type=network_type, qp_type=bnxt.CMDQ_MODIFY_QP_QP_TYPE_RC, access=BNXT_ACCESS,
|
||||||
|
max_rd_atomic=1)
|
||||||
|
|
||||||
|
if BNXT_DEBUG >= 1: print(f"bnxt: QP {self.qpn:#x} connected (remote={qpn:#x})")
|
||||||
|
|
||||||
|
def _poll(self, timeout):
|
||||||
|
def poll():
|
||||||
|
base = bnxt.struct_cq_base.from_buffer_copy(bytes(_qread(self.cqq, self.cqq["cons"])))
|
||||||
|
return bool(base.cqe_type_toggle & bnxt.CQ_BASE_TOGGLE) == (not bool((self.cqq["cons"] // 16) & 1))
|
||||||
|
wait_cond(poll, timeout_ms=timeout, msg="BNXT CQ")
|
||||||
|
raw = bytes(_qread(self.cqq, self.cqq["cons"]))
|
||||||
|
self.cqq["cons"] += 1
|
||||||
|
self.dev.doorbell(self.cq_id, bnxt.DBC_DBC_TYPE_CQ, self.cqq["cons"] & 15, (self.cqq["cons"] // 16) & 1)
|
||||||
|
return raw
|
||||||
|
|
||||||
|
def rdma_write(self, rva, rkey, lva, lkey, size, timeout_ms=20000):
|
||||||
|
start = self.sq["prod"] & 15
|
||||||
|
hdr = bytes(bnxt.struct_sq_rdma_hdr(wqe_type=bnxt.SQ_RDMA_HDR_WQE_TYPE_WRITE_WQE,
|
||||||
|
flags=bnxt.SQ_SEND_FLAGS_SIGNAL_COMP, wqe_size=3, length=size, remote_va=rva, remote_key=rkey))
|
||||||
|
for i, data in enumerate((hdr[:16], hdr[16:32], bytes(bnxt.struct_sq_sge(va_or_pa=lva, l_key=lkey, size=size)))):
|
||||||
|
_qwrite(self.sq, start + i, data)
|
||||||
|
nxt = (self.sq_psn + max(1, ceildiv(size, 1024))) & 0xffffff
|
||||||
|
value = start << bnxt.SQ_MSN_SEARCH_START_IDX_SFT | nxt << bnxt.SQ_MSN_SEARCH_NEXT_PSN_SFT | self.sq_psn
|
||||||
|
_qwrite(self.sq, self.msn, struct.pack("<Q", value), aux=True)
|
||||||
|
|
||||||
|
self.msn, self.sq_psn, self.sq["prod"] = (self.msn + 1) % 128, nxt, self.sq["prod"] + 3
|
||||||
|
self.dev.doorbell(self.qpn, bnxt.DBC_DBC_TYPE_SQ, self.sq["prod"] & 15, (self.sq["prod"] // 16) & 1)
|
||||||
|
cqe = bnxt.struct_cq_req.from_buffer_copy(self._poll(timeout_ms))
|
||||||
|
assert cqe.status == 0
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Send and validate one RDMA WRITE between two Broadcom BNXT hosts.
|
||||||
|
|
||||||
|
This follows ``extra/mlx_driver/connect.py``: sync the driver, start the remote
|
||||||
|
endpoint over SSH, exchange QP/GID/MAC/MR metadata, move both RC QPs to RTS,
|
||||||
|
write bytes into the remote MR, and verify the bytes on the remote host.
|
||||||
|
|
||||||
|
Both PCI functions must be unbound from bnxt_en/bnxt_re first.
|
||||||
|
"""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from typing import Any, IO
|
||||||
|
|
||||||
|
TINYGRAD = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "../.."))
|
||||||
|
sys.path.insert(0, TINYGRAD)
|
||||||
|
|
||||||
|
from extra.bnxt_driver.bnxtdev import BNXTDev, BNXTQP
|
||||||
|
from tinygrad.runtime.support.system import PCIDevice
|
||||||
|
|
||||||
|
REMOTE_HOST = os.getenv("REMOTE_HOST", "192.168.52.213")
|
||||||
|
REMOTE_USER = os.getenv("REMOTE_USER", "nimlgen")
|
||||||
|
LOCAL_PCI = os.getenv("BNXT_PCI", "0000:41:00.0")
|
||||||
|
REMOTE_PCI = os.getenv("REMOTE_PCI", "0000:41:00.0")
|
||||||
|
LOCAL_IP = os.getenv("LOCAL_IP", "10.0.200.5")
|
||||||
|
REMOTE_IP = os.getenv("REMOTE_IP", "10.0.200.6")
|
||||||
|
MESSAGE = os.getenv("RDMA_MESSAGE", "Test message, rdma works!").encode()
|
||||||
|
REMOTE = f"{REMOTE_USER}@{REMOTE_HOST}"
|
||||||
|
SSH = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-o", "StrictHostKeyChecking=accept-new", REMOTE]
|
||||||
|
SYNC_FILES = ("tinygrad/runtime/autogen/bnxt.py", "tinygrad/runtime/support/system.py",
|
||||||
|
"extra/bnxt_driver/bnxtdev.py", "extra/bnxt_driver/connect.py")
|
||||||
|
|
||||||
|
def read_json(stream:IO[str], what:str) -> dict[str, Any]:
|
||||||
|
for line in iter(stream.readline, ""):
|
||||||
|
print(f" [remote] {line}", end="")
|
||||||
|
try: value = json.loads(line)
|
||||||
|
except json.JSONDecodeError: continue
|
||||||
|
if isinstance(value, dict): return value
|
||||||
|
raise RuntimeError(f"remote exited before publishing {what}")
|
||||||
|
|
||||||
|
def wait_line(stream:IO[str], text:str) -> str:
|
||||||
|
for line in iter(stream.readline, ""):
|
||||||
|
print(f" [remote] {line}", end="")
|
||||||
|
if text in line: return line
|
||||||
|
raise RuntimeError(f"remote exited before reporting {text!r}")
|
||||||
|
|
||||||
|
def send_line(stream:IO[str], value:str|dict[str, Any]):
|
||||||
|
stream.write((json.dumps(value) if isinstance(value, dict) else value) + "\n")
|
||||||
|
stream.flush()
|
||||||
|
|
||||||
|
def qp_info(dev:BNXTDev, qp:BNXTQP) -> dict[str, Any]:
|
||||||
|
return {"qpn":qp.qpn, "mac":dev.mac.to_bytes(6, "big").hex(), "gid":dev.local_gid.hex()}
|
||||||
|
|
||||||
|
def server():
|
||||||
|
dev = BNXTDev(PCIDevice("bnxt", os.getenv("BNXT_PCI", "0000:41:00.0")), ip=os.getenv("BNXT_IP", REMOTE_IP))
|
||||||
|
qp = BNXTQP(dev)
|
||||||
|
print(json.dumps(qp_info(dev, qp)), flush=True)
|
||||||
|
|
||||||
|
peer = json.loads(sys.stdin.readline())
|
||||||
|
qp.connect(peer["qpn"], bytes.fromhex(peer["gid"]), int(peer["mac"], 16))
|
||||||
|
print("connected", flush=True)
|
||||||
|
|
||||||
|
target, target_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||||
|
target[:0x1000] = bytes(0x1000)
|
||||||
|
rkey = dev.register_mem(target_paddrs, 0x1000)
|
||||||
|
print(json.dumps({"target_addr":target_paddrs[0], "rkey":rkey}), flush=True)
|
||||||
|
|
||||||
|
assert sys.stdin.readline().strip() == "done"
|
||||||
|
received = bytes(target).rstrip(b"\0")
|
||||||
|
print(f"AS TEXT: {received.decode(errors='replace')!r}", flush=True)
|
||||||
|
print(json.dumps({"data":received.hex()}), flush=True)
|
||||||
|
|
||||||
|
def sync_remote():
|
||||||
|
if os.getenv("SYNC", "1") == "0": return
|
||||||
|
print("syncing BNXT driver to remote")
|
||||||
|
subprocess.run(["rsync", "-azR", *SYNC_FILES, f"{REMOTE}:~/tinygrad/"], cwd=TINYGRAD, check=True)
|
||||||
|
|
||||||
|
def start_remote() -> subprocess.Popen[str]:
|
||||||
|
print("booting remote")
|
||||||
|
command = (f"cd ~/tinygrad && sudo env PYTHONPATH=. PYTHONUNBUFFERED=1 BNXT_DEBUG={os.getenv('BNXT_DEBUG', '0')} "
|
||||||
|
f"BNXT_PCI={REMOTE_PCI} BNXT_IP={REMOTE_IP} python3 extra/bnxt_driver/connect.py --server")
|
||||||
|
return subprocess.Popen(SSH + [command], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=sys.stderr, text=True)
|
||||||
|
|
||||||
|
def client():
|
||||||
|
assert 0 < len(MESSAGE) <= 0x1000
|
||||||
|
sync_remote()
|
||||||
|
remote = start_remote()
|
||||||
|
assert remote.stdin is not None and remote.stdout is not None
|
||||||
|
remote_info = read_json(remote.stdout, "QP information")
|
||||||
|
print("booting local")
|
||||||
|
dev = BNXTDev(PCIDevice("bnxt", LOCAL_PCI), ip=LOCAL_IP)
|
||||||
|
qp = BNXTQP(dev)
|
||||||
|
|
||||||
|
send_line(remote.stdin, qp_info(dev, qp))
|
||||||
|
wait_line(remote.stdout, "connected")
|
||||||
|
qp.connect(remote_info["qpn"], bytes.fromhex(remote_info["gid"]), int(remote_info["mac"], 16))
|
||||||
|
print("both QPs in RTS")
|
||||||
|
|
||||||
|
remote_target = read_json(remote.stdout, "MR information")
|
||||||
|
source, source_paddrs = dev.pci_dev.alloc_sysmem(0x1000)
|
||||||
|
source[:len(MESSAGE)] = MESSAGE
|
||||||
|
lkey = dev.register_mem(source_paddrs, 0x1000)
|
||||||
|
print(f"RDMA WRITE {len(MESSAGE)}B to remote phys 0x{remote_target['target_addr']:x}")
|
||||||
|
qp.rdma_write(remote_target["target_addr"], remote_target["rkey"], source_paddrs[0], lkey, len(MESSAGE))
|
||||||
|
|
||||||
|
send_line(remote.stdin, "done")
|
||||||
|
wait_line(remote.stdout, "AS TEXT")
|
||||||
|
result = read_json(remote.stdout, "RDMA result")
|
||||||
|
assert bytes.fromhex(result["data"]) == MESSAGE
|
||||||
|
print("RDMA WRITE data verified")
|
||||||
|
|
||||||
|
remote.stdin.close()
|
||||||
|
assert remote.wait() == 0
|
||||||
|
print("RDMA WRITE test complete")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server() if "--server" in sys.argv else client()
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Local BNXT RoCEv2 RDMA WRITE loopback using the firmware's PHY loopback mode.
|
||||||
|
|
||||||
|
The kernel bnxt_en/bnxt_re modules must be unloaded first.
|
||||||
|
|
||||||
|
sudo PYTHONPATH=. BNXT_PCI=0000:41:00.0 BNXT_IP=10.0.200.5 python3 extra/bnxt_driver/loopback.py
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "../.."))
|
||||||
|
|
||||||
|
from extra.bnxt_driver.bnxtdev import BNXTDev, BNXTQP
|
||||||
|
from tinygrad.runtime.autogen import bnxt
|
||||||
|
from tinygrad.runtime.support.system import PCIDevice
|
||||||
|
|
||||||
|
BUF_SIZE = 0x1000
|
||||||
|
BNXT_PCI = os.getenv("BNXT_PCI", "0000:41:00.0")
|
||||||
|
BNXT_IP = os.getenv("BNXT_IP", "10.0.200.5")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(f"[init] BNXT at {BNXT_PCI}")
|
||||||
|
dev = BNXTDev(PCIDevice("bnxt", BNXT_PCI), ip=BNXT_IP)
|
||||||
|
tx_qp, rx_qp = BNXTQP(dev), BNXTQP(dev)
|
||||||
|
print(f"[init] loopback-connect TX QP 0x{tx_qp.qpn:x} <-> RX QP 0x{rx_qp.qpn:x}")
|
||||||
|
tx_qp.connect(rx_qp.qpn, dev.local_gid, dev.mac)
|
||||||
|
rx_qp.connect(tx_qp.qpn, dev.local_gid, dev.mac)
|
||||||
|
|
||||||
|
src, src_paddrs = dev.pci_dev.alloc_sysmem(BUF_SIZE)
|
||||||
|
dst, dst_paddrs = dev.pci_dev.alloc_sysmem(BUF_SIZE)
|
||||||
|
message = b"Hello from BNXT RoCE PHY loopback!"
|
||||||
|
src[:BUF_SIZE], dst[:BUF_SIZE] = bytes(BUF_SIZE), bytes(BUF_SIZE)
|
||||||
|
src[:len(message)] = message
|
||||||
|
lkey = dev.register_mem(src_paddrs, BUF_SIZE)
|
||||||
|
rkey = dev.register_mem(dst_paddrs, BUF_SIZE)
|
||||||
|
|
||||||
|
print("[loopback] enabling local PHY loopback")
|
||||||
|
dev.hwrm("port_phy_cfg", port_id=dev.port_id, enables=bnxt.PORT_PHY_CFG_REQ_ENABLES_LPBK, lpbk=bnxt.PORT_PHY_CFG_REQ_LPBK_LOCAL)
|
||||||
|
time.sleep(1)
|
||||||
|
tx_qp.rdma_write(dst_paddrs[0], rkey, src_paddrs[0], lkey, len(message))
|
||||||
|
got = bytes(dst[:len(message)])
|
||||||
|
print(f"[result] {got!r}")
|
||||||
|
assert got == message
|
||||||
|
print("BNXT RoCE PHY loopback RDMA WRITE passed")
|
||||||
|
dev.hwrm("port_phy_cfg", port_id=dev.port_id, enables=bnxt.PORT_PHY_CFG_REQ_ENABLES_LPBK, lpbk=bnxt.PORT_PHY_CFG_REQ_LPBK_NONE)
|
||||||
@@ -35,7 +35,7 @@ def compile_net(linear:UOp, output_bufs:List[Buffer]) -> Tuple[Dict[str,str], Li
|
|||||||
return name
|
return name
|
||||||
|
|
||||||
for call in iter_kernel_calls(linear):
|
for call in iter_kernel_calls(linear):
|
||||||
arg_uops = [b for b in call.src[1:] if b.op is not Ops.BIND]
|
arg_uops = [b for b in call.src[1:] if not b.is_bound_var]
|
||||||
prg = to_program(call.src[0], Device[arg_uops[0].device].renderer)
|
prg = to_program(call.src[0], Device[arg_uops[0].device].renderer)
|
||||||
info = prg.arg
|
info = prg.arg
|
||||||
functions[info.function_name] = prg.src[2].arg
|
functions[info.function_name] = prg.src[2].arg
|
||||||
@@ -241,8 +241,7 @@ export default {model_name};
|
|||||||
def export_model(model, target:str, *inputs, model_name: Optional[str] = "model", stream_weights=False):
|
def export_model(model, target:str, *inputs, model_name: Optional[str] = "model", stream_weights=False):
|
||||||
assert Device.DEFAULT in EXPORT_SUPPORTED_DEVICE, f"only {', '.join(EXPORT_SUPPORTED_DEVICE)} are supported"
|
assert Device.DEFAULT in EXPORT_SUPPORTED_DEVICE, f"only {', '.join(EXPORT_SUPPORTED_DEVICE)} are supported"
|
||||||
|
|
||||||
# NOTE: NUM_CPU_THREADS=1, since export does not support threading
|
with Context(JIT=2): linear, output_bufs = jit_model(model, *inputs)
|
||||||
with Context(JIT=2, NUM_CPU_THREADS=1): linear, output_bufs = jit_model(model, *inputs)
|
|
||||||
functions, statements, bufs, bufs_to_save = compile_net(linear, output_bufs)
|
functions, statements, bufs, bufs_to_save = compile_net(linear, output_bufs)
|
||||||
state = get_state_dict(model)
|
state = get_state_dict(model)
|
||||||
weight_names = {(id(b), b.offset, b.size, b.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
|
weight_names = {(id(b), b.offset, b.size, b.dtype): name for name, x in state.items() if (b:=x.uop.base.realized) is not None}
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ def test_matmul():
|
|||||||
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||||
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs, arg=KernelInfo(name=colored("kernel", "cyan"),
|
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs, arg=KernelInfo(name=colored("kernel", "cyan"),
|
||||||
estimates=Estimates(ops=N*N*N*2, mem=N*N*4*3)))
|
estimates=Estimates(ops=N*N*N*2, mem=N*N*4*3)))
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||||
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
||||||
linear = c.schedule_linear()
|
linear = c.schedule_linear()
|
||||||
|
|
||||||
|
|||||||
@@ -122,9 +122,10 @@ def custom_mxfp4_gemm(C:UOp, A:UOp, B:UOp, scale_a:UOp, scale_b:UOp, *extra:UOp,
|
|||||||
groups_x, groups_y = UOp.special(ceildiv(N, tile_n), "gidx0"), UOp.special(ceildiv(M, tile_m), "gidx1")
|
groups_x, groups_y = UOp.special(ceildiv(N, tile_n), "gidx0"), UOp.special(ceildiv(M, tile_m), "gidx1")
|
||||||
lds = UOp.placeholder((163840,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
lds = UOp.placeholder((163840,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||||
sink = UOp.sink(C.base, A.base, B.base, scale_a.base, scale_b.base, *(x.base for x in extra), lds, threads, groups_x, groups_y,
|
sink = UOp.sink(C.base, A.base, B.base, scale_a.base, scale_b.base, *(x.base for x in extra), lds, threads, groups_x, groups_y,
|
||||||
arg=KernelInfo(f"custom_mxfp4_gemm_{M}_{N}_{K}", estimates=Estimates(ops=2*M*N*K)))
|
arg=KernelInfo(f"mxfp4_gemm_{M}_{N}_{K}",
|
||||||
|
estimates=Estimates(ops=2*M*N*K, mem=(M*half_k+N*half_k)*A.dtype.itemsize+M*N*C.dtype.itemsize)))
|
||||||
insts = build_kernel(M, N, K, tile_m, tile_n)
|
insts = build_kernel(M, N, K, tile_m, tile_n)
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple(UOp(Ops.INS, arg=x) for x in insts))))
|
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple(UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts))))
|
||||||
|
|
||||||
def _mxfp4_gemm_quantized(a_q:Tensor, b_q:Tensor, scale_a:Tensor, scale_b:Tensor) -> Tensor:
|
def _mxfp4_gemm_quantized(a_q:Tensor, b_q:Tensor, scale_a:Tensor, scale_b:Tensor) -> Tensor:
|
||||||
M, half_k = a_q.shape
|
M, half_k = a_q.shape
|
||||||
@@ -214,7 +215,7 @@ def custom_uop_gemm(C:UOp, A:UOp, B:UOp) -> UOp:
|
|||||||
k = UOp.range(K, 0, AxisType.REDUCE)
|
k = UOp.range(K, 0, AxisType.REDUCE)
|
||||||
mul = (A.flatten().index((m*UOp.const(K)+k))*
|
mul = (A.flatten().index((m*UOp.const(K)+k))*
|
||||||
B.flatten().index((k*UOp.const(N)+n))).cast(dtypes.float32)
|
B.flatten().index((k*UOp.const(N)+n))).cast(dtypes.float32)
|
||||||
red = mul.reduce(k, arg=Ops.ADD, dtype=dtypes.float32).cast(C.dtype)
|
red = mul.reduce(k, arg=Ops.ADD).cast(C.dtype)
|
||||||
store = C.flatten().index((m*UOp.const(N)+n)).store(red).end(m, n)
|
store = C.flatten().index((m*UOp.const(N)+n)).store(red).end(m, n)
|
||||||
return store.sink(arg=KernelInfo(name=f'uop_gemm_{M}_{N}_{K}'))
|
return store.sink(arg=KernelInfo(name=f'uop_gemm_{M}_{N}_{K}'))
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -113,7 +113,6 @@ if __name__ == "__main__":
|
|||||||
}
|
}
|
||||||
elif GEMM_VARIATION == "hcopt" and M == N == K == 4096 and DTYPE_IN == dtypes.half and DTYPE_OUT == dtypes.half and DTYPE_ACC == dtypes.float:
|
elif GEMM_VARIATION == "hcopt" and M == N == K == 4096 and DTYPE_IN == dtypes.half and DTYPE_OUT == dtypes.half and DTYPE_ACC == dtypes.float:
|
||||||
print("Using CUDA and generated hcopt")
|
print("Using CUDA and generated hcopt")
|
||||||
# [Opt(op=OptOps.TC, axis=0, amt=0), Opt(op=OptOps.UPCAST, axis=0, amt=4), Opt(op=OptOps.UPCAST, axis=1, amt=4), Opt(op=OptOps.LOCAL, axis=1, amt=4)]
|
|
||||||
prog = CUDAProgram(device, "wmma_example", compiler.compile(open(os.path.join(script_dir, 'max_kernels/nv.fp16_fp32_fp16.hcopt.cu')).read()))
|
prog = CUDAProgram(device, "wmma_example", compiler.compile(open(os.path.join(script_dir, 'max_kernels/nv.fp16_fp32_fp16.hcopt.cu')).read()))
|
||||||
args = (c, a, b)
|
args = (c, a, b)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
|
|||||||
@@ -1,10 +1,32 @@
|
|||||||
import functools, pathlib
|
import functools, pathlib
|
||||||
from tinygrad import Tensor, dtypes
|
from tinygrad import Tensor, dtypes
|
||||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
from tinygrad.uop.ops import UOp, Ops, KernelInfo, AxisType
|
||||||
|
from tinygrad.helpers import getenv
|
||||||
from tinygrad.renderer import Estimates
|
from tinygrad.renderer import Estimates
|
||||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||||
from extra.gemm.cdna_asm_gemm import quantize_mxfp8, _mx_block_scale, _mx_block_scale_3d
|
from extra.gemm.cdna_asm_gemm import quantize_mxfp8, _mx_block_scale, _mx_block_scale_3d
|
||||||
|
|
||||||
|
ZERO_OPTIM = getenv("ZERO_OPTIM", 0)
|
||||||
|
|
||||||
|
def reduce_scatter_devaxis(out:Tensor, shard_axis:int=0) -> Tensor:
|
||||||
|
# out: sharded on the device axis, shape (ndev, *rest); return the device-axis sum left sharded on shard_axis.
|
||||||
|
u = out.uop
|
||||||
|
devs, rest = u.device, u.shape[1:]
|
||||||
|
assert rest[shard_axis] % len(devs) == 0, f"reduce_scatter needs even shards: {rest[shard_axis]} % {len(devs)}"
|
||||||
|
# reach the raw per-device buffer below the UNSHARD, keeping the AFTERs so reads stay ordered after the kernel writes
|
||||||
|
node, barriers = u, []
|
||||||
|
while node.op is not Ops.UNSHARD:
|
||||||
|
if node.op is Ops.AFTER: barriers += node.src[1:]
|
||||||
|
node = node.src[0]
|
||||||
|
mbuf = node.src[0].after(*barriers) if barriers else node.src[0]
|
||||||
|
sz = rest[shard_axis] // len(devs)
|
||||||
|
shards = []
|
||||||
|
for i in range(len(devs)):
|
||||||
|
bounds = tuple((0,s) if a != shard_axis else (i*sz,(i+1)*sz) for a,s in enumerate(rest))
|
||||||
|
contribs = [mbuf.mselect(j).reshape(rest).shrink(bounds).copy_to_device(devs[i]) for j in range(len(devs))]
|
||||||
|
shards.append(functools.reduce(lambda a,b: a.alu(Ops.ADD, b), contribs))
|
||||||
|
return Tensor(UOp.mstack(*shards).unshard(shard_axis, UOp.range(len(devs), -1, AxisType.DEVICE)), device=devs)
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def custom_hk_grouped_mxfp8_gemm(C:UOp, A:UOp, B:UOp, scale_A:UOp, scale_B:UOp, *extra:UOp, dname:str, n_experts:int) -> UOp:
|
def custom_hk_grouped_mxfp8_gemm(C:UOp, A:UOp, B:UOp, scale_A:UOp, scale_B:UOp, *extra:UOp, dname:str, n_experts:int) -> UOp:
|
||||||
M, K = A.shape
|
M, K = A.shape
|
||||||
@@ -58,7 +80,8 @@ def grouped_mx_wgrad(g:Tensor, xg:Tensor, expert_off:Tensor, n_experts:int) -> T
|
|||||||
out = Tensor(inv.uop.unshard(0), device=g.device) if is_multi else inv
|
out = Tensor(inv.uop.unshard(0), device=g.device) if is_multi else inv
|
||||||
out = Tensor.custom_kernel(out, gT, xT, g_si, x_si, expert_off,
|
out = Tensor.custom_kernel(out, gT, xT, g_si, x_si, expert_off,
|
||||||
fxn=functools.partial(custom_hk_grouped_mxfp8_wgrad, dname=dname, n_experts=n_experts))[0]
|
fxn=functools.partial(custom_hk_grouped_mxfp8_wgrad, dname=dname, n_experts=n_experts))[0]
|
||||||
out = out.sum(0) if is_multi else out.squeeze(0)
|
if is_multi and ZERO_OPTIM: out = reduce_scatter_devaxis(out, 0)
|
||||||
|
else: out = out.sum(0) if is_multi else out.squeeze(0)
|
||||||
return out.reshape(n_experts, N, K)
|
return out.reshape(n_experts, N, K)
|
||||||
|
|
||||||
def mx_pack_3d(e8:Tensor) -> Tensor:
|
def mx_pack_3d(e8:Tensor) -> Tensor:
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def _ggather_bwd(gradient:UOp, kernel:UOp) -> tuple:
|
|||||||
g, m, j, jo, ji = _kv_ranges(Gk, M, Dk, _blk_for(Dk))
|
g, m, j, jo, ji = _kv_ranges(Gk, M, Dk, _blk_for(Dk))
|
||||||
row = idx.index(g, m).cast(dtypes.weakint)
|
row = idx.index(g, m).cast(dtypes.weakint)
|
||||||
val = gout.index(g, m, j).load().cast(dtypes.float32)
|
val = gout.index(g, m, j).load().cast(dtypes.float32)
|
||||||
atomic = UOp(Ops.CUSTOM, dtypes.void, (gtab.index(g, row, j), val), arg=atomic_str)
|
atomic = UOp(Ops.CUSTOM, src=(gtab.index(g, row, j), val), arg=(atomic_str, dtypes.void))
|
||||||
return atomic.end(g, m, jo, ji).sink(arg=KernelInfo(name=f"ggather_bwd_{M}_{Dk}", opts_to_apply=()))
|
return atomic.end(g, m, jo, ji).sink(arg=KernelInfo(name=f"ggather_bwd_{M}_{Dk}", opts_to_apply=()))
|
||||||
grad_table = Tensor.custom_kernel(gt, go, Tensor(idx_u, device=dev), fxn=_bwd_kernel)[0]
|
grad_table = Tensor.custom_kernel(gt, go, Tensor(idx_u, device=dev), fxn=_bwd_kernel)[0]
|
||||||
return (None, grad_table.cast(table_u.dtype).uop, None)
|
return (None, grad_table.cast(table_u.dtype).uop, None)
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ def test_matmul():
|
|||||||
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
lds = UOp.placeholder((lds_size,), dtypes.uint8, 0, AddrSpace.LOCAL)
|
||||||
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs,
|
sink = UOp.sink(A.base, B.base, C.base, lds, *gidxs, *lidxs,
|
||||||
arg=KernelInfo(name=colored("kernel","cyan"), estimates=Estimates(ops=N*N*N*2, mem=N*N*2*3)))
|
arg=KernelInfo(name=colored("kernel","cyan"), estimates=Estimates(ops=N*N*N*2, mem=N*N*2*3)))
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||||
|
|
||||||
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
c = Tensor.custom_kernel(a, b, c, fxn=asm_kernel)[2]
|
||||||
linear = c.schedule_linear()
|
linear = c.schedule_linear()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from tinygrad import Tensor, dtypes, Context
|
from tinygrad import Tensor, dtypes, Context
|
||||||
from tinygrad.helpers import getenv
|
from tinygrad.helpers import getenv
|
||||||
from tinygrad.codegen.opt import Opt, OptOps
|
from tinygrad.codegen.opt import Opt, OptOps
|
||||||
|
from tinygrad.uop.ops import AxisType
|
||||||
from tinygrad.engine.realize import run_linear
|
from tinygrad.engine.realize import run_linear
|
||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
|
|
||||||
@@ -13,17 +14,17 @@ if __name__ == "__main__":
|
|||||||
C = A.matmul(B)
|
C = A.matmul(B)
|
||||||
if getenv("GEMV"):
|
if getenv("GEMV"):
|
||||||
opts = [
|
opts = [
|
||||||
Opt(op=OptOps.UNROLL, axis=0, amt=8),
|
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UNROLL)),
|
||||||
Opt(op=OptOps.GROUP, axis=0, amt=32),
|
Opt(op=OptOps.SPLIT, axis=1, arg=(32, AxisType.GROUP_REDUCE)),
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
opts = [
|
opts = [
|
||||||
Opt(op=OptOps.TC, axis=0, amt=0),
|
Opt(op=OptOps.TC, axis=0, amt=0),
|
||||||
Opt(op=OptOps.UPCAST, axis=0, amt=4),
|
Opt(op=OptOps.SPLIT, axis=0, arg=(4, AxisType.UPCAST)),
|
||||||
Opt(op=OptOps.UPCAST, axis=1, amt=8),
|
Opt(op=OptOps.SPLIT, axis=1, arg=(8, AxisType.UPCAST)),
|
||||||
Opt(op=OptOps.LOCAL, axis=0, amt=2),
|
Opt(op=OptOps.SPLIT, axis=0, arg=(2, AxisType.LOCAL)),
|
||||||
Opt(op=OptOps.LOCAL, axis=1, amt=2),
|
Opt(op=OptOps.SPLIT, axis=1, arg=(2, AxisType.LOCAL)),
|
||||||
Opt(op=OptOps.LOCAL, axis=0, amt=2),
|
Opt(op=OptOps.SPLIT, axis=0, arg=(2, AxisType.LOCAL)),
|
||||||
]
|
]
|
||||||
linear = C.schedule_linear()
|
linear = C.schedule_linear()
|
||||||
call = linear.src[-1]
|
call = linear.src[-1]
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ if __name__ == "__main__":
|
|||||||
linear, var_vals = C.linear_with_vars()
|
linear, var_vals = C.linear_with_vars()
|
||||||
last_call = linear.src[-1]
|
last_call = linear.src[-1]
|
||||||
ast = last_call.src[0]
|
ast = last_call.src[0]
|
||||||
bufs = [s.buffer for s in last_call.src[1:] if s.op is not Ops.BIND]
|
bufs = [s.buffer for s in last_call.src[1:] if not s.is_bound_var]
|
||||||
|
|
||||||
src = compiled.asm["ptx"]
|
src = compiled.asm["ptx"]
|
||||||
# specify the shared memory here so we don't need to do it dynamically
|
# specify the shared memory here so we don't need to do it dynamically
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
import functools
|
||||||
|
from tinygrad import Tensor
|
||||||
|
from tinygrad.uop.ops import UOp
|
||||||
|
|
||||||
|
def rmsnorm_mul_fwd(x_in:Tensor, weight:Tensor, eps:float) -> tuple[Tensor, Tensor]:
|
||||||
|
x = x_in.float()
|
||||||
|
rrms = (x.square().mean(-1, keepdim=True) + eps).rsqrt()
|
||||||
|
return ((x * rrms) * weight.float()).cast(x_in.dtype), rrms
|
||||||
|
|
||||||
|
@functools.cache
|
||||||
|
def _rmsnorm_mul_fwd_fxn(x_in_p, w_p, eps, device):
|
||||||
|
return rmsnorm_mul_fwd(Tensor(x_in_p, device=device), Tensor(w_p, device=device), eps)
|
||||||
|
|
||||||
|
def _rmsnorm_mul_bwd(grad:UOp, call:UOp) -> tuple:
|
||||||
|
x = Tensor(call.src[1]).float(); weight = Tensor(call.src[2]).float()
|
||||||
|
rrms = Tensor(call.gettuple(1))
|
||||||
|
x_normed = x * rrms # recompute unweighted normed (x is call.src[1])
|
||||||
|
d_y = Tensor(grad).float()
|
||||||
|
dxn = d_y * weight # d/d(x_normed)
|
||||||
|
d_x = rrms * (dxn - x_normed * (dxn * x_normed).mean(-1, keepdim=True))
|
||||||
|
dw = d_y * x_normed
|
||||||
|
d_weight = dw.sum(axis=tuple(range(dw.ndim - 1))) # reduce batch/seq -> [dim]
|
||||||
|
return (d_x.cast(call.src[1].dtype).uop, d_weight.cast(call.src[2].dtype).uop)
|
||||||
|
|
||||||
|
def rmsnorm_mul(x_in:Tensor, weight:Tensor, eps:float) -> tuple[Tensor, Tensor]:
|
||||||
|
fxn = _rmsnorm_mul_fwd_fxn(x_in.as_param(0).uop, weight.as_param(1).uop, eps, x_in.device)
|
||||||
|
call = UOp.maketuple(fxn[0].uop, fxn[1].uop).call(x_in.uop, weight.uop, grad_fxn=_rmsnorm_mul_bwd)
|
||||||
|
return Tensor(call.gettuple(0)), Tensor(call.gettuple(1))
|
||||||
@@ -16,9 +16,12 @@ def _do_reset_device(pci_bus): os.system(f"sudo sh -c 'echo 1 > /sys/bus/pci/dev
|
|||||||
def _is_module_loaded(name: str) -> bool: return os.path.isdir(f"/sys/module/{name}")
|
def _is_module_loaded(name: str) -> bool: return os.path.isdir(f"/sys/module/{name}")
|
||||||
|
|
||||||
def cmd_remove_module(args):
|
def cmd_remove_module(args):
|
||||||
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia", "ast"] if args.backend == "nv" else ["amdgpu"]
|
modules = ["nvidia_drm", "nvidia_modeset", "nvidia_uvm", "nvidia"] if args.backend == "nv" else ["amdgpu"]
|
||||||
to_unload = [m for m in modules if _is_module_loaded(m)]
|
to_unload = [m for m in modules if _is_module_loaded(m)]
|
||||||
if not to_unload: print("Kernel modules are not loaded")
|
if not to_unload: print("Kernel modules are not loaded")
|
||||||
|
elif getattr(args, "expect", False):
|
||||||
|
print(f"Kernel modules are loaded: {to_unload}")
|
||||||
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print("Removing kernel modules:", ", ".join(to_unload))
|
print("Removing kernel modules:", ", ".join(to_unload))
|
||||||
try: subprocess.run(["sudo", "modprobe", "-r", *to_unload], check=True)
|
try: subprocess.run(["sudo", "modprobe", "-r", *to_unload], check=True)
|
||||||
@@ -60,17 +63,19 @@ def cmd_show_pids(args):
|
|||||||
|
|
||||||
def cmd_kill_pids(args):
|
def cmd_kill_pids(args):
|
||||||
devs = scan_devs_based_on_lock(prefix:={"amd":"am", "nv":"nv"}[args.backend], args)
|
devs = scan_devs_based_on_lock(prefix:={"amd":"am", "nv":"nv"}[args.backend], args)
|
||||||
|
use_sudo = not getattr(args, "sudoless", False)
|
||||||
|
|
||||||
for dev in devs:
|
for dev in devs:
|
||||||
for i in range(128):
|
for i in range(128):
|
||||||
if i > 0: time.sleep(0.2)
|
if i > 0: time.sleep(0.2)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try: pid = subprocess.check_output(['sudo', 'lsof', temp(f'{prefix}_{dev}.lock')]).decode('utf-8').strip().split('\n')[1].split()[1]
|
try: pid = subprocess.check_output((['sudo'] if use_sudo else []) +
|
||||||
|
['lsof', temp(f'{prefix}_{dev}.lock')]).decode('utf-8').strip().split('\n')[1].split()[1]
|
||||||
except subprocess.CalledProcessError: break
|
except subprocess.CalledProcessError: break
|
||||||
|
|
||||||
print(f"Killing process {pid} (which uses {dev})")
|
print(f"Killing process {pid} (which uses {dev})")
|
||||||
subprocess.run(['sudo', 'kill', '-9', pid], check=True)
|
subprocess.run((['sudo'] if use_sudo else []) + ['kill', '-9', pid], check=True)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Failed to kill process for device {dev}: {e}", file=sys.stderr)
|
print(f"Failed to kill process for device {dev}: {e}", file=sys.stderr)
|
||||||
|
|
||||||
@@ -79,6 +84,7 @@ def add_common_commands(parent_subparsers):
|
|||||||
p_insmod.set_defaults(func=cmd_insert_module)
|
p_insmod.set_defaults(func=cmd_insert_module)
|
||||||
|
|
||||||
p_rmmod = parent_subparsers.add_parser("rmmod", help="Remove a kernel module")
|
p_rmmod = parent_subparsers.add_parser("rmmod", help="Remove a kernel module")
|
||||||
|
p_rmmod.add_argument("--expect", action="store_true", help="Just assert that module is already unloaded")
|
||||||
p_rmmod.set_defaults(func=cmd_remove_module)
|
p_rmmod.set_defaults(func=cmd_remove_module)
|
||||||
|
|
||||||
p_reset = parent_subparsers.add_parser("reset", help="Reset a device")
|
p_reset = parent_subparsers.add_parser("reset", help="Reset a device")
|
||||||
@@ -91,17 +97,20 @@ def add_common_commands(parent_subparsers):
|
|||||||
|
|
||||||
p_reset = parent_subparsers.add_parser("kill_pids", help="Kill pids of processes using the device")
|
p_reset = parent_subparsers.add_parser("kill_pids", help="Kill pids of processes using the device")
|
||||||
p_reset.add_argument("--pci_bus", default="", help="PCI bus ID of the device")
|
p_reset.add_argument("--pci_bus", default="", help="PCI bus ID of the device")
|
||||||
|
p_reset.add_argument("--sudoless", action="store_true", help="Do not use sudo when detecting or killing pids")
|
||||||
p_reset.set_defaults(func=cmd_kill_pids)
|
p_reset.set_defaults(func=cmd_kill_pids)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
backend_subparsers = parser.add_subparsers(dest="backend", required=True, metavar="{nv,amd}", help="Hardware backend to target")
|
backend_subparsers = parser.add_subparsers(dest="backend", required=True, metavar="{nv,amd}", help="Hardware backend to target")
|
||||||
|
|
||||||
nv_parser = backend_subparsers.add_parser("nv", help="NVIDIA GPUs")
|
nv_parser = backend_subparsers.add_parser("nv", aliases=["NV"], help="NVIDIA GPUs")
|
||||||
|
nv_parser.set_defaults(backend="nv")
|
||||||
nv_commands = nv_parser.add_subparsers(dest="command", required=True)
|
nv_commands = nv_parser.add_subparsers(dest="command", required=True)
|
||||||
add_common_commands(nv_commands)
|
add_common_commands(nv_commands)
|
||||||
|
|
||||||
amd_parser = backend_subparsers.add_parser("amd", help="AMD GPUs")
|
amd_parser = backend_subparsers.add_parser("amd", aliases=["AMD"], help="AMD GPUs")
|
||||||
|
amd_parser.set_defaults(backend="amd")
|
||||||
amd_commands = amd_parser.add_subparsers(dest="command", required=True)
|
amd_commands = amd_parser.add_subparsers(dest="command", required=True)
|
||||||
add_common_commands(amd_commands)
|
add_common_commands(amd_commands)
|
||||||
|
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from typing import cast, Any, Callable
|
from typing import cast
|
||||||
import os, ctypes, struct, hashlib, functools, importlib, mmap, errno, array, contextlib, sys, weakref, itertools, collections, atexit
|
import os, ctypes, struct, functools, importlib, mmap, errno, contextlib, sys, itertools, atexit
|
||||||
assert sys.platform != 'win32'
|
assert sys.platform != 'win32'
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from tinygrad.runtime.support.hcq2 import HCQ2Compiled, HCQAllocator, HCQ2Buffer, encode_kernargs_clike, make_cmdbuf
|
from tinygrad.runtime.support.hcq2 import HCQ2Compiled, HCQAllocator, make_buf, hcq_size_var
|
||||||
from tinygrad.runtime.support.hcq2 import make_binary_patch
|
|
||||||
from tinygrad.uop.ops import sint, UOp
|
from tinygrad.uop.ops import sint, UOp
|
||||||
from tinygrad.device import Compiled, BufferSpec, Buffer, Device
|
from tinygrad.device import BufferSpec, Buffer, Device
|
||||||
from tinygrad.dtype import dtypes
|
from tinygrad.dtype import dtypes
|
||||||
from tinygrad.helpers import getenv, round_up, data64_le, DEBUG, PROFILE, ProfileEvent, lo32, hi32, colored, prod, ContextVar, TracingKey
|
from tinygrad.helpers import getenv, round_up, data64_le, DEBUG, PROFILE, lo32, hi32
|
||||||
from tinygrad.helpers import VIZ, ceildiv, unwrap, pluralize, to_tuple
|
from tinygrad.helpers import ceildiv, unwrap, pluralize, to_tuple
|
||||||
from tinygrad.renderer.cstyle import HIPRenderer, HIPCCRenderer
|
from tinygrad.renderer.cstyle import HIPRenderer, HIPCCRenderer
|
||||||
from tinygrad.renderer.llvmir import AMDLLVMRenderer
|
from tinygrad.renderer.llvmir import AMDLLVMRenderer
|
||||||
from tinygrad.runtime.autogen import kfd, hsa, sqtt, amdgpu_kd, amdgpu_drm
|
from tinygrad.runtime.autogen import kfd, hsa, amdgpu_kd, amdgpu_drm
|
||||||
from tinygrad.runtime.autogen.am import am
|
from tinygrad.runtime.autogen.am import am
|
||||||
from tinygrad.runtime.support.elf import elf_loader
|
from tinygrad.runtime.support.elf import elf_loader
|
||||||
from tinygrad.runtime.support.hcq import FileIOInterface, HCQBuffer, MMIOInterface, hcq_filter_visible_devices
|
from tinygrad.runtime.support.hcq import FileIOInterface, HCQBuffer, MMIOInterface, hcq_filter_visible_devices
|
||||||
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager
|
from tinygrad.runtime.support.am.amdev import AMDev, AMMemoryManager
|
||||||
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, import_soc, import_pmc
|
from tinygrad.runtime.support.amd import AMDReg, AMDIP, import_module, import_soc, import_pmc
|
||||||
from tinygrad.runtime.support.system import PCIIfaceBase, PCIAllocationMeta, USBPCIDevice, MAP_FIXED, MAP_NORESERVE
|
from tinygrad.runtime.support.system import PCIIfaceBase, PCIAllocationMeta, USBPCIDevice, MAP_FIXED, MAP_NORESERVE
|
||||||
from tinygrad.runtime.support.usb import USB3
|
from tinygrad.runtime.support.usb import USB3, pm_usb_bufferize
|
||||||
from tinygrad.runtime.support.memory import AddrSpace, BumpAllocator
|
from tinygrad.runtime.support.memory import AddrSpace, BumpAllocator
|
||||||
from tinygrad.runtime.ops_amd import SQTT, SQTT_ITRACE_SE_MASK, SQTT_LIMIT_SE, SQTT_SIMD_SEL, SQTT_TOKEN_EXCLUDE, PMC
|
from tinygrad.runtime.ops_amd import SQTT, PMC
|
||||||
from tinygrad.runtime.ops_amd import EVENT_INDEX_PARTIAL_FLUSH, WAIT_REG_MEM_FUNCTION_EQ, WAIT_REG_MEM_FUNCTION_NEQ, WAIT_REG_MEM_FUNCTION_GEQ
|
from tinygrad.runtime.ops_amd import EVENT_INDEX_PARTIAL_FLUSH, WAIT_REG_MEM_FUNCTION_GEQ
|
||||||
if getenv("IOCTL"): import extra.hip_gpu_driver.hip_ioctl # noqa: F401 # pylint: disable=unused-import
|
if getenv("IOCTL"): import extra.hip_gpu_driver.hip_ioctl # noqa: F401 # pylint: disable=unused-import
|
||||||
|
|
||||||
from tinygrad.engine.realize import get_runtime, pm_flatten_linear
|
from tinygrad.engine.realize import get_call_arg_uops, get_call_var_uops
|
||||||
from tinygrad.uop import FastEnum, auto
|
from tinygrad.uop import FastEnum, auto
|
||||||
from tinygrad.uop.ops import Ops, UPat, PatternMatcher, graph_rewrite
|
from tinygrad.uop.ops import Ops, UPat, PatternMatcher
|
||||||
|
|
||||||
# *****************
|
# *****************
|
||||||
# PM4
|
# PM4
|
||||||
@@ -36,9 +35,11 @@ class PM4Ops(FastEnum):
|
|||||||
SET_SH_REG = auto(); SET_UCONFIG_REG = auto(); WAIT_REG_MEM = auto(); ACQUIRE_MEM = auto() # noqa: E702
|
SET_SH_REG = auto(); SET_UCONFIG_REG = auto(); WAIT_REG_MEM = auto(); ACQUIRE_MEM = auto() # noqa: E702
|
||||||
RELEASE_MEM = auto(); DISPATCH_DIRECT = auto(); EVENT_WRITE = auto() # noqa: E702
|
RELEASE_MEM = auto(); DISPATCH_DIRECT = auto(); EVENT_WRITE = auto() # noqa: E702
|
||||||
|
|
||||||
|
def _dw(vals) -> int: return sum(2 if isinstance(x, UOp) and x.dtype.itemsize == 8 else 1 for x in vals)
|
||||||
|
|
||||||
def pkt3(ctx, op:PM4Ops, *vals):
|
def pkt3(ctx, op:PM4Ops, *vals):
|
||||||
return UOp(Ops.INS, arg=op, src=tuple(UOp.const(x, dtypes.uint32)
|
return UOp(Ops.LINEAR, src=tuple(x if isinstance(x, UOp) else UOp.const(x, dtypes.uint32)
|
||||||
for x in (ctx.pm4.PACKET3(getattr(ctx.pm4, f"PACKET3_{op.name}"), len(vals) - 1), *vals)))
|
for x in (ctx.pm4.PACKET3(getattr(ctx.pm4, f"PACKET3_{op.name}"), _dw(vals) - 1), *vals)))
|
||||||
|
|
||||||
def wreg(ctx, reg:AMDReg, *args:sint, **kwargs:int):
|
def wreg(ctx, reg:AMDReg, *args:sint, **kwargs:int):
|
||||||
if bool(args) == bool(kwargs): raise RuntimeError('One (and only one) of *args or **kwargs must be specified')
|
if bool(args) == bool(kwargs): raise RuntimeError('One (and only one) of *args or **kwargs must be specified')
|
||||||
@@ -52,7 +53,7 @@ def wreg(ctx, reg:AMDReg, *args:sint, **kwargs:int):
|
|||||||
def wait_reg_mem(ctx, value, mask=0xffffffff, mem=None, reg=None, reg_done=0, op=WAIT_REG_MEM_FUNCTION_GEQ):
|
def wait_reg_mem(ctx, value, mask=0xffffffff, mem=None, reg=None, reg_done=0, op=WAIT_REG_MEM_FUNCTION_GEQ):
|
||||||
wrm_info_dw = ctx.pm4.WAIT_REG_MEM_MEM_SPACE(int(mem is not None)) | ctx.pm4.WAIT_REG_MEM_OPERATION(int(mem is None and reg_done > 0)) \
|
wrm_info_dw = ctx.pm4.WAIT_REG_MEM_MEM_SPACE(int(mem is not None)) | ctx.pm4.WAIT_REG_MEM_OPERATION(int(mem is None and reg_done > 0)) \
|
||||||
| ctx.pm4.WAIT_REG_MEM_FUNCTION(op) | ctx.pm4.WAIT_REG_MEM_ENGINE(0)
|
| ctx.pm4.WAIT_REG_MEM_FUNCTION(op) | ctx.pm4.WAIT_REG_MEM_ENGINE(0)
|
||||||
return pkt3(ctx, PM4Ops.WAIT_REG_MEM, wrm_info_dw, *(data64_le(mem) if mem is not None else (reg, reg_done)), value, mask, 4)
|
return pkt3(ctx, PM4Ops.WAIT_REG_MEM, wrm_info_dw, *((mem,) if mem is not None else (reg, reg_done)), value, mask, 4)
|
||||||
|
|
||||||
def acquire_mem(ctx, addr=0x0, sz=(1 << 64)-1, gli=1, glm=1, glk=1, glv=1, gl1=1, gl2=1):
|
def acquire_mem(ctx, addr=0x0, sz=(1 << 64)-1, gli=1, glm=1, glk=1, glv=1, gl1=1, gl2=1):
|
||||||
if ctx.target[0] != 9:
|
if ctx.target[0] != 9:
|
||||||
@@ -83,16 +84,18 @@ def release_mem(ctx, address=0x0, value=0, data_sel=0, int_sel=2, ctxid=0, cache
|
|||||||
event_dw = ctx.pm4.EVENT_TYPE(ctx.pm4.CACHE_FLUSH_AND_INV_TS_EVENT) | ctx.pm4.EVENT_INDEX(ctx.pm4.event_index__mec_release_mem__end_of_pipe)
|
event_dw = ctx.pm4.EVENT_TYPE(ctx.pm4.CACHE_FLUSH_AND_INV_TS_EVENT) | ctx.pm4.EVENT_INDEX(ctx.pm4.event_index__mec_release_mem__end_of_pipe)
|
||||||
memsel_dw = ctx.pm4.DATA_SEL(data_sel) | ctx.pm4.INT_SEL(int_sel)
|
memsel_dw = ctx.pm4.DATA_SEL(data_sel) | ctx.pm4.INT_SEL(int_sel)
|
||||||
ctxid = 0
|
ctxid = 0
|
||||||
return pkt3(ctx, PM4Ops.RELEASE_MEM, event_dw | cache_flags_dw, memsel_dw, *data64_le(address), *data64_le(value), ctxid)
|
addr_w = address if isinstance(address, UOp) else UOp.const(address, dtypes.uint64)
|
||||||
|
val_w = value.cast(dtypes.uint64) if isinstance(value, UOp) else UOp.const(value, dtypes.uint64)
|
||||||
|
return pkt3(ctx, PM4Ops.RELEASE_MEM, event_dw | cache_flags_dw, memsel_dw, addr_w, val_w, ctxid)
|
||||||
|
|
||||||
def memory_barrier(ctx):
|
def memory_barrier(ctx):
|
||||||
pf = '' if ctx.nbio.version[0] == 2 else '0' if ctx.nbio.version[:2] != (7, 11) else '1'
|
pf = '' if ctx.nbio.version[0] == 2 else '0' if ctx.nbio.version[:2] != (7, 11) else '1'
|
||||||
return UOp(Ops.LINEAR, dtypes.void, (
|
return UOp(Ops.LINEAR, src=(
|
||||||
wait_reg_mem(ctx, reg=getattr(ctx.nbio, f'regBIF_BX_PF{pf}_GPU_HDP_FLUSH_REQ').addr[0],
|
wait_reg_mem(ctx, reg=getattr(ctx.nbio, f'regBIF_BX_PF{pf}_GPU_HDP_FLUSH_REQ').addr[0],
|
||||||
reg_done=getattr(ctx.nbio, f'regBIF_BX_PF{pf}_GPU_HDP_FLUSH_DONE').addr[0], value=0xffffffff),
|
reg_done=getattr(ctx.nbio, f'regBIF_BX_PF{pf}_GPU_HDP_FLUSH_DONE').addr[0], value=0xffffffff),
|
||||||
acquire_mem(ctx)))
|
acquire_mem(ctx)))
|
||||||
|
|
||||||
def pm4_wait(ctx, dst, val): return wait_reg_mem(ctx, val, mem=dst.getaddr(ctx.devs))
|
def pm4_wait(ctx, dst, val): return wait_reg_mem(ctx, val.cast(dtypes.uint32), mem=dst.getaddr(ctx.devs))
|
||||||
|
|
||||||
def pm4_barrier(ctx): return memory_barrier(ctx)
|
def pm4_barrier(ctx): return memory_barrier(ctx)
|
||||||
|
|
||||||
@@ -106,155 +109,130 @@ def pm4_timestamp(ctx, dst):
|
|||||||
ctx.pm4.int_sel__mec_release_mem__none)
|
ctx.pm4.int_sel__mec_release_mem__none)
|
||||||
|
|
||||||
def pm4_program(ctx, call, prg):
|
def pm4_program(ctx, call, prg):
|
||||||
data, info = prg.arg
|
data, lib = amd_build_program(ctx.dev, prg)
|
||||||
lib_gpu = prg.src[0]
|
info = prg.arg
|
||||||
args = encode_kernargs_clike(call, prg, ctx.devs)
|
|
||||||
prog_addr = lib_gpu.getaddr(ctx.devs) + data.entry_point_offset
|
|
||||||
scratch_addr = UOp.placeholder((data.private_segment_size,), dtypes.uint8, 0, device=ctx.devs).rtag("scratch").getaddr(ctx.devs)
|
|
||||||
args_addr = args.getaddr(ctx.devs)
|
|
||||||
|
|
||||||
user_regs = []
|
# kernargs: a nested blob linear inside a getaddr, input addresses and variable values are filled per call through the input table
|
||||||
if data.enable_private_segment_sgpr:
|
ka_words = [get_call_arg_uops(call)[gi].getaddr(ctx.devs) for gi in info.globals] + list(get_call_var_uops(call, prg))
|
||||||
scratch_hilo = data64_le(scratch_addr)
|
pad = data.kernargs_alloc_size - sum(w.dtype.itemsize for w in ka_words)
|
||||||
user_regs = [scratch_hilo[0], scratch_hilo[1] | 1 << 31, 0xffffffff, 0x20c14000]
|
assert pad >= 0 and pad % 4 == 0, f"bad kernargs padding {pad}"
|
||||||
if data.enable_dispatch_ptr: user_regs += [*data64_le(args_addr + data.kernargs_segment_size)]
|
ka = UOp(Ops.LINEAR, src=tuple(ka_words) + (UOp.const(0, dtypes.uint32),) * (pad // 4)).rtag("kernargs")
|
||||||
user_regs += [*data64_le(args_addr)]
|
|
||||||
|
prog_addr = lib.getaddr(ctx.devs) + data.entry_point_offset
|
||||||
|
scratch_addr = UOp.placeholder((data.private_segment_size,), dtypes.uint8, 0, device=ctx.devs).rtag("scratch").getaddr(ctx.devs)
|
||||||
|
args_addr = ka.getaddr(ctx.devs)
|
||||||
|
|
||||||
|
user_regs:list = []
|
||||||
|
if data.enable_private_segment_sgpr: user_regs = [scratch_addr | (1 << 63), 0xffffffff, 0x20c14000]
|
||||||
|
if data.enable_dispatch_ptr: user_regs += [args_addr + data.kernargs_segment_size]
|
||||||
|
user_regs += [args_addr]
|
||||||
|
|
||||||
dispatch_init = ctx.gc.regCOMPUTE_DISPATCH_INITIATOR.encode(
|
dispatch_init = ctx.gc.regCOMPUTE_DISPATCH_INITIATOR.encode(
|
||||||
**({'cs_w32_en': int(data.wave32)} if ctx.target[0] != 9 else {}), force_start_at_000=1, compute_shader_en=1)
|
**({'cs_w32_en': int(data.wave32)} if ctx.target[0] != 9 else {}), force_start_at_000=1, compute_shader_en=1)
|
||||||
ins = [acquire_mem(ctx, gli=0, gl2=0),
|
ins = [acquire_mem(ctx, gli=0, gl2=0),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_PGM_LO, *data64_le(prog_addr >> 8)),
|
wreg(ctx, ctx.gc.regCOMPUTE_PGM_LO, prog_addr >> 8),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_PGM_RSRC1, data.rsrc1, data.rsrc2),
|
wreg(ctx, ctx.gc.regCOMPUTE_PGM_RSRC1, data.rsrc1, data.rsrc2),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_PGM_RSRC3, data.rsrc3),
|
wreg(ctx, ctx.gc.regCOMPUTE_PGM_RSRC3, data.rsrc3),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_TMPRING_SIZE, ctx.tmpring_size(data.private_segment_size))]
|
wreg(ctx, ctx.gc.regCOMPUTE_TMPRING_SIZE, ctx.tmpring_size(data.private_segment_size))]
|
||||||
ins += [wreg(ctx, ctx.gc.regCOMPUTE_DISPATCH_SCRATCH_BASE_LO, *data64_le((scratch_addr + data.private_segment_size // ctx.xccs * xcc_id) >> 8))
|
ins += [wreg(ctx, ctx.gc.regCOMPUTE_DISPATCH_SCRATCH_BASE_LO, (scratch_addr + data.private_segment_size // ctx.xccs * xcc_id) >> 8)
|
||||||
for xcc_id in range(ctx.xccs)]
|
for xcc_id in range(ctx.xccs)]
|
||||||
ins += [wreg(ctx, ctx.gc.regCOMPUTE_RESTART_X, 0, 0, 0),
|
ins += [wreg(ctx, ctx.gc.regCOMPUTE_RESTART_X, 0, 0, 0),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_USER_DATA_0, *user_regs),
|
wreg(ctx, ctx.gc.regCOMPUTE_USER_DATA_0, *user_regs),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_RESOURCE_LIMITS, ctx.gc.regCOMPUTE_RESOURCE_LIMITS.encode(waves_per_sh=getenv("WAVES_PER_SH"))),
|
wreg(ctx, ctx.gc.regCOMPUTE_RESOURCE_LIMITS, ctx.gc.regCOMPUTE_RESOURCE_LIMITS.encode(waves_per_sh=getenv("WAVES_PER_SH"))),
|
||||||
wreg(ctx, ctx.gc.regCOMPUTE_START_X, 0, 0, 0, *(info.local_size or (1, 1, 1)), 0, 0),
|
wreg(ctx, ctx.gc.regCOMPUTE_START_X, 0, 0, 0, *info.local_size, 0, 0),
|
||||||
pkt3(ctx, PM4Ops.DISPATCH_DIRECT, *info.global_size, dispatch_init),
|
pkt3(ctx, PM4Ops.DISPATCH_DIRECT, *info.global_size, dispatch_init),
|
||||||
pkt3(ctx, PM4Ops.EVENT_WRITE, ctx.pm4.EVENT_TYPE(ctx.soc.CS_PARTIAL_FLUSH) | ctx.pm4.EVENT_INDEX(EVENT_INDEX_PARTIAL_FLUSH))]
|
pkt3(ctx, PM4Ops.EVENT_WRITE, ctx.pm4.EVENT_TYPE(ctx.soc.CS_PARTIAL_FLUSH) | ctx.pm4.EVENT_INDEX(EVENT_INDEX_PARTIAL_FLUSH))]
|
||||||
return UOp(Ops.LINEAR, dtypes.void, tuple(ins))
|
return UOp(Ops.LINEAR, src=tuple(ins))
|
||||||
|
|
||||||
pm_pm4_opsel = PatternMatcher([
|
def pm4_ib(ctx, submit:UOp, lin:UOp) -> UOp|None:
|
||||||
|
# the ring only carries a packet pointing at the ib: the host fence at the start of the batch guarantees the ib is free to reuse
|
||||||
|
if lin.tag is not None or any(w.op in {Ops.CALL, Ops.INS, Ops.LINEAR, Ops.NOOP} for w in lin.src): return None # wait for the flat word linear
|
||||||
|
assert (size_dw:=sum(w.dtype.itemsize for w in lin.src) // 4) < (1 << 20), f"indirect buffer of {size_dw} dwords doesn't fit one packet"
|
||||||
|
pkt = (UOp.const(ctx.pm4.PACKET3(ctx.pm4.PACKET3_INDIRECT_BUFFER, 2), dtypes.uint32), lin.rtag("indirect").getaddr(ctx.devs),
|
||||||
|
UOp.const(size_dw | ctx.pm4.INDIRECT_BUFFER_VALID, dtypes.uint32))
|
||||||
|
return submit.replace(src=(UOp(Ops.LINEAR, src=pkt, arg=lin.arg).rtag(("cmdbuf", ctx.queue)),))
|
||||||
|
|
||||||
|
pm_pm4_encode = PatternMatcher([
|
||||||
(UPat(Ops.CALL, src=(UPat(Ops.PROGRAM, name="prg"),), name="call", allow_any_len=True), pm4_program),
|
(UPat(Ops.CALL, src=(UPat(Ops.PROGRAM, name="prg"),), name="call", allow_any_len=True), pm4_program),
|
||||||
|
(UPat(Ops.CUSTOM_FUNCTION, arg="submit_cmdbuf", src=(UPat(Ops.LINEAR, name="lin"),), name="submit"), pm4_ib),
|
||||||
|
|
||||||
(UPat(Ops.INS, arg="wait", src=(UPat(name="dst"), UPat(name="val"))), pm4_wait),
|
(UPat(Ops.INS, arg=("barrier", dtypes.void)), pm4_barrier),
|
||||||
(UPat(Ops.INS, arg="barrier"), pm4_barrier),
|
(UPat(Ops.INS, arg=("wait", dtypes.void), src=(UPat(name="dst"), UPat(name="val"))), pm4_wait),
|
||||||
(UPat(Ops.INS, arg="timestamp", src=(UPat(name="dst"),)), pm4_timestamp),
|
(UPat(Ops.INS, arg=("timestamp", dtypes.void), src=(UPat(name="dst"),)), pm4_timestamp),
|
||||||
(UPat(Ops.INS, arg="store", src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val"))), pm4_store),
|
(UPat(Ops.INS, arg=("store", dtypes.void), src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val"))), pm4_store),
|
||||||
])
|
])
|
||||||
|
|
||||||
def pm4_submit(ctx, lin):
|
|
||||||
# ensure compute queues are allocated
|
|
||||||
for d in (devs:=ctx.devs): q = Device[d].compute_queue
|
|
||||||
ring, wptr, doorbell, put_ptr = (UOp.placeholder((b.size,), b.dtype, 0, device=devs).rtag(f"COMPUTE:0_{name}")
|
|
||||||
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
|
|
||||||
|
|
||||||
# the host fence at the start of the batch guarantees the ib is free to reuse
|
|
||||||
size_dw = sum(len(ins.src) for ins in lin.src)
|
|
||||||
assert size_dw < (1 << 20), f"indirect buffer of {size_dw} dwords doesn't fit one packet"
|
|
||||||
|
|
||||||
ib = UOp.placeholder((size_dw,), dtypes.uint32, next(UOp.unique_num), device=devs, volatile=True).rtag("cmdbuf")
|
|
||||||
cmdbuf = make_cmdbuf(lin, devs, buf=ib)
|
|
||||||
|
|
||||||
# the ring itself only carries a packet pointing at the ib, wrapping the ring
|
|
||||||
put = put_ptr.index(zero:=UOp.const(0, dtypes.int))
|
|
||||||
pkt = (ctx.pm4.PACKET3(ctx.pm4.PACKET3_INDIRECT_BUFFER, 2), *data64_le(cmdbuf.getaddr(devs)), size_dw | ctx.pm4.INDIRECT_BUFFER_VALID)
|
|
||||||
write_pkt = UOp.barrier(*[ring.index(((put + off) % q.ring.size).cast(dtypes.int)).store(UOp.const(x, dtypes.uint32)) for off,x in enumerate(pkt)])
|
|
||||||
|
|
||||||
# advance the put/write pointers past the packet
|
|
||||||
bump_put_ptr = put_ptr.index(zero).store(put + len(pkt))
|
|
||||||
bump_wptr = wptr.index(zero).store(put + len(pkt))
|
|
||||||
flush = UOp.barrier(write_pkt, bump_put_ptr, bump_wptr)
|
|
||||||
return doorbell.after(flush).index(zero).store(put + len(pkt))
|
|
||||||
|
|
||||||
pm_pm4_submit = PatternMatcher([(UPat(Ops.LINEAR, name="lin"), pm4_submit)])
|
|
||||||
|
|
||||||
# *****************
|
# *****************
|
||||||
# SDMA
|
# SDMA
|
||||||
|
|
||||||
class SDMAOps(FastEnum): COPY = auto(); POLL_REGMEM = auto(); FENCE = auto(); TRAP = auto(); TIMESTAMP = auto() # noqa: E702
|
|
||||||
|
|
||||||
def sdma_copy(ctx, call):
|
def sdma_copy(ctx, call):
|
||||||
sz = call.src[2].max_numel() * call.src[2].dtype.itemsize
|
sz = call.src[2].max_numel() * call.src[2].dtype.itemsize
|
||||||
src_addr, dst_addr = call.src[2].getaddr(ctx.devs), call.src[1].getaddr(ctx.devs)
|
hdr = ctx.sdma.SDMA_OP_COPY | ctx.sdma.SDMA_PKT_COPY_LINEAR_HEADER_SUB_OP(ctx.sdma.SDMA_SUBOP_COPY_LINEAR)
|
||||||
return call.ins(SDMAOps.COPY, src=tuple(UOp.const(x, dtypes.uint32) for off in range(0, sz, ctx.max_copy_size) for x in (
|
return UOp(Ops.LINEAR, src=tuple(x for off in range(0, sz, ctx.max_copy_size) for x in (
|
||||||
ctx.sdma.SDMA_OP_COPY | ctx.sdma.SDMA_PKT_COPY_LINEAR_HEADER_SUB_OP(ctx.sdma.SDMA_SUBOP_COPY_LINEAR),
|
*(UOp.const(v, dtypes.uint32) for v in (hdr, ctx.sdma.SDMA_PKT_COPY_LINEAR_COUNT_COUNT(min(sz-off, ctx.max_copy_size)-1), 0)),
|
||||||
ctx.sdma.SDMA_PKT_COPY_LINEAR_COUNT_COUNT(min(sz-off, ctx.max_copy_size)-1), 0,
|
*(a + UOp.const(off, dtypes.uint64) if off else a for a in (call.src[2].getaddr(ctx.devs), call.src[1].getaddr(ctx.devs))))))
|
||||||
*data64_le(src_addr+UOp.const(off, dtypes.uint64)), *data64_le(dst_addr+UOp.const(off, dtypes.uint64)))))
|
|
||||||
|
|
||||||
def sdma_wait(ctx, ins, dst, val):
|
def sdma_wait(ctx, dst, val):
|
||||||
op = ctx.sdma.SDMA_OP_POLL_REGMEM | ctx.sdma.SDMA_PKT_POLL_REGMEM_HEADER_FUNC(WAIT_REG_MEM_FUNCTION_GEQ) \
|
op = ctx.sdma.SDMA_OP_POLL_REGMEM | ctx.sdma.SDMA_PKT_POLL_REGMEM_HEADER_FUNC(WAIT_REG_MEM_FUNCTION_GEQ) \
|
||||||
| ctx.sdma.SDMA_PKT_POLL_REGMEM_HEADER_MEM_POLL(1)
|
| ctx.sdma.SDMA_PKT_POLL_REGMEM_HEADER_MEM_POLL(1)
|
||||||
return ins.ins(SDMAOps.POLL_REGMEM, src=tuple(UOp.const(x, dtypes.uint32) for x in (
|
|
||||||
op, *data64_le(dst.getaddr(ctx.devs)), val, 0xffffffff,
|
|
||||||
ctx.sdma.SDMA_PKT_POLL_REGMEM_DW5_INTERVAL(0x04) | ctx.sdma.SDMA_PKT_POLL_REGMEM_DW5_RETRY_COUNT(0xfff))))
|
|
||||||
|
|
||||||
def sdma_store(ctx, ins, dst, val):
|
|
||||||
op = ctx.sdma.SDMA_OP_FENCE | (ctx.sdma.SDMA_PKT_FENCE_HEADER_MTYPE(3) if ctx.target[0] != 9 else 0)
|
|
||||||
return UOp(Ops.LINEAR, src=(
|
return UOp(Ops.LINEAR, src=(
|
||||||
ins.ins(SDMAOps.FENCE, src=tuple(UOp.const(x, dtypes.uint32) for x in (op, *data64_le(dst.getaddr(ctx.devs)), val))),
|
UOp.const(op, dtypes.uint32), dst.getaddr(ctx.devs), val.cast(dtypes.uint32), UOp.const(0xffffffff, dtypes.uint32),
|
||||||
ins.ins(SDMAOps.TRAP, src=tuple(UOp.const(x, dtypes.uint32) for x in (ctx.sdma.SDMA_OP_TRAP, 0)))))
|
UOp.const(ctx.sdma.SDMA_PKT_POLL_REGMEM_DW5_INTERVAL(0x04) | ctx.sdma.SDMA_PKT_POLL_REGMEM_DW5_RETRY_COUNT(0xfff), dtypes.uint32)))
|
||||||
|
|
||||||
def sdma_timestamp(ctx, ins, dst):
|
def sdma_store(ctx, dst, val): # a fence packet then a trap
|
||||||
|
op = ctx.sdma.SDMA_OP_FENCE | (ctx.sdma.SDMA_PKT_FENCE_HEADER_MTYPE(3) if ctx.target[0] != 9 else 0)
|
||||||
|
return UOp(Ops.LINEAR, src=(UOp.const(op, dtypes.uint32), dst.getaddr(ctx.devs), val.cast(dtypes.uint32),
|
||||||
|
UOp.const(ctx.sdma.SDMA_OP_TRAP, dtypes.uint32), UOp.const(0, dtypes.uint32)))
|
||||||
|
|
||||||
|
def sdma_timestamp(ctx, dst):
|
||||||
op = ctx.sdma.SDMA_OP_TIMESTAMP | ctx.sdma.SDMA_PKT_TIMESTAMP_GET_HEADER_SUB_OP(ctx.sdma.SDMA_SUBOP_TIMESTAMP_GET_GLOBAL)
|
op = ctx.sdma.SDMA_OP_TIMESTAMP | ctx.sdma.SDMA_PKT_TIMESTAMP_GET_HEADER_SUB_OP(ctx.sdma.SDMA_SUBOP_TIMESTAMP_GET_GLOBAL)
|
||||||
return ins.ins(SDMAOps.TIMESTAMP, src=tuple(UOp.const(x, dtypes.uint32) for x in (op, *data64_le(dst.getaddr(ctx.devs)))))
|
return UOp(Ops.LINEAR, src=(UOp.const(op, dtypes.uint32), dst.getaddr(ctx.devs)))
|
||||||
|
|
||||||
pm_sdma_opsel = PatternMatcher([
|
pm_sdma_encode = PatternMatcher([
|
||||||
(UPat(Ops.CALL, src=(UPat(Ops.COPY),), name="call", allow_any_len=True), sdma_copy),
|
(UPat(Ops.CALL, src=(UPat(Ops.COPY),), name="call", allow_any_len=True), sdma_copy),
|
||||||
|
|
||||||
(UPat(Ops.INS, arg="barrier"), lambda: UOp(Ops.NOOP, dtypes.void, ())),
|
(UPat(Ops.INS, arg=("barrier", dtypes.void)), lambda: UOp(Ops.LINEAR)),
|
||||||
(UPat(Ops.INS, arg="wait", src=(UPat(name="dst"), UPat(name="val")), name="ins"), sdma_wait),
|
(UPat(Ops.INS, arg=("wait", dtypes.void), src=(UPat(name="dst"), UPat(name="val"))), sdma_wait),
|
||||||
(UPat(Ops.INS, arg="timestamp", src=(UPat(name="dst"),), name="ins"), sdma_timestamp),
|
(UPat(Ops.INS, arg=("timestamp", dtypes.void), src=(UPat(name="dst"),)), sdma_timestamp),
|
||||||
(UPat(Ops.INS, arg="store", src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val")), name="ins"), sdma_store),
|
(UPat(Ops.INS, arg=("store", dtypes.void), src=(UPat((Ops.BUFFER, Ops.PARAM), name="dst"), UPat(name="val"))), sdma_store),
|
||||||
])
|
])
|
||||||
|
|
||||||
def sdma_submit(cmdbuf, devs):
|
# *****************
|
||||||
# the cmdbuf to submit + the patch writes that fill it
|
# queue submit
|
||||||
size_dw, zero = cmdbuf.nbytes() // dtypes.uint32.itemsize, UOp.const(0, dtypes.int)
|
|
||||||
|
|
||||||
# the sdma queue's ring and its host-side ring/write/put pointers
|
def _queue_bufs(ctx, q:AMDQueueDesc) -> tuple[UOp, UOp, UOp, UOp]:
|
||||||
for d in devs: q = Device[d].sdma_queue(0)
|
ring = UOp.placeholder((q.ring.size,), q.ring.dtype, 0, device=ctx.devs, volatile=True).rtag(f"{ctx.queue}_ring")
|
||||||
ring, wptr, doorbell, put_ptr = (UOp.placeholder((b.size,), b.dtype, 0, device=devs).rtag(f"COPY:0_{name}")
|
return (ring, *(make_buf(ctx.devs, tag=f"{ctx.queue}_{n}") for n in ("write_ptr", "doorbell", "put_value")))
|
||||||
for name, b in (("ring", q.ring), ("write_ptr", q.write_ptr), ("doorbell", q.doorbell), ("put_value", q.put_value)))
|
|
||||||
|
|
||||||
# sdma needs the cmdbuf contiguous: if it won't fit before the ring end, restart at 0 and zero the tail
|
def pm4_submit(ctx, cmdbuf:UOp) -> UOp:
|
||||||
put_b = put_ptr.index(zero)
|
for d in ctx.devs: q = Device[d].compute_queue
|
||||||
tail_off_dw = ((put_b % (q.ring.size * 4)) // 4).cast(dtypes.int)
|
ring, wptr, doorbell, put = _queue_bufs(ctx, q)
|
||||||
fits = (size_dw <= q.ring.size - tail_off_dw).cast(dtypes.int)
|
p, size_dw = put.after(cmdbuf).index(0).load(), hcq_size_var(cmdbuf) // 4
|
||||||
start_dw = fits * tail_off_dw
|
i = UOp.range(size_dw, 10, dtype=dtypes.int, src=(cmdbuf, ring))
|
||||||
zero_amt_dw = (1 - fits) * (q.ring.size - tail_off_dw)
|
copy = ring.index(((p + i.cast(p.dtype)) % q.ring.size).cast(dtypes.int)).store(cmdbuf.bitcast(dtypes.uint32).index(i).load()).end(i)
|
||||||
|
next_put = p + size_dw.cast(p.dtype)
|
||||||
|
flush = UOp.barrier(copy, put.index(0).store(next_put), wptr.index(0).store(next_put))
|
||||||
|
return doorbell.after(flush).index(0).store(next_put)
|
||||||
|
|
||||||
# zero the wrapped tail, then copy the cmdbuf into the ring
|
def sdma_submit(ctx, cmdbuf:UOp) -> UOp:
|
||||||
zi = UOp.range(zero_amt_dw, 0, dtype=dtypes.int, src=(cmdbuf,))
|
# sdma needs the cmdbuf contiguous in the ring: if it won't fit before the ring end, restart at 0 and zero the tail
|
||||||
zero_tail = ring.index(tail_off_dw + zi).store(UOp.const(0, dtypes.uint32)).end(zi)
|
for d in ctx.devs: q = unwrap(Device[d].sdma_queue(int(ctx.queue.split(":")[1])))
|
||||||
i = UOp.range(UOp.const(size_dw, dtypes.int), 0, dtype=dtypes.int, src=(cmdbuf,))
|
(ring, wptr, doorbell, put), rs = _queue_bufs(ctx, q), q.ring.size
|
||||||
copy_to_ring = ring.index(start_dw + i).store(cmdbuf.index(i).load()).end(i)
|
size_dw = hcq_size_var(cmdbuf) // 4
|
||||||
|
put_b = put.after(cmdbuf).index(0).load()
|
||||||
|
tail = ((put_b % (rs * 4)) // 4).cast(dtypes.int)
|
||||||
|
fits = (size_dw <= rs - tail).cast(dtypes.int)
|
||||||
|
start_dw, zero_amt = fits * tail, (1 - fits) * (rs - tail)
|
||||||
|
zi = UOp.range(zero_amt, 10, dtype=dtypes.int, src=(ring,))
|
||||||
|
zero_tail = ring.index(tail + zi).store(UOp.const(0, dtypes.uint32)).end(zi)
|
||||||
|
i = UOp.range(size_dw, 11, dtype=dtypes.int, src=(cmdbuf, ring))
|
||||||
|
copy = ring.index(start_dw + i).store(cmdbuf.bitcast(dtypes.uint32).index(i).load()).end(i)
|
||||||
|
next_put = put_b + ((zero_amt + size_dw) * 4).cast(put_b.dtype)
|
||||||
|
flush = UOp.barrier(zero_tail, copy, put.index(0).store(next_put), wptr.index(0).store(next_put))
|
||||||
|
return doorbell.after(flush).index(0).store(next_put)
|
||||||
|
|
||||||
# advance the put/write pointers past the zeroed tail and the cmdbuf
|
pm_pm4_submit = PatternMatcher([(UPat(Ops.CUSTOM_FUNCTION, arg="submit_cmdbuf", src=(UPat(name="cmdbuf"),)), pm4_submit)])
|
||||||
next_put_b = put_b + ((zero_amt_dw + size_dw) * 4).cast(put_b.dtype)
|
pm_sdma_submit = PatternMatcher([(UPat(Ops.CUSTOM_FUNCTION, arg="submit_cmdbuf", src=(UPat(name="cmdbuf"),)), sdma_submit)])
|
||||||
bump_put_ptr = put_ptr.index(zero).store(next_put_b)
|
|
||||||
bump_wptr = wptr.index(zero).store(next_put_b)
|
|
||||||
|
|
||||||
# ring the doorbell once the writes have landed
|
|
||||||
flush = UOp.barrier(zero_tail, copy_to_ring, bump_put_ptr, bump_wptr)
|
|
||||||
return doorbell.after(flush).index(zero).store(next_put_b)
|
|
||||||
|
|
||||||
pm_sdma_submit = PatternMatcher([(UPat(Ops.LINEAR, name="lin"),
|
|
||||||
lambda ctx, lin: sdma_submit(make_cmdbuf(lin, ctx.devs), ctx.devs))])
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class AMDEncodeCtx: # encode-time constants for one queue: devs (every cmdbuf address resolves into these) + gfx version + packet/ip modules
|
|
||||||
devs: tuple[str, ...]; target: tuple[int, ...]; pm4: Any; sdma: Any; soc: Any # noqa: E702
|
|
||||||
gc: AMDIP; nbio: AMDIP; xccs: int; max_copy_size: int; tmpring_size: Callable # noqa: E702
|
|
||||||
|
|
||||||
def encode_queue(q:UOp) -> UOp|None:
|
|
||||||
d = Device[(devs:=to_tuple(q.arg[0]))[0]]
|
|
||||||
ctx = AMDEncodeCtx(devs, d.target, d.pm4, d.sdma, d.soc, d.gc, d.nbio, d.xccs, d.max_copy_size, d.tmpring_size)
|
|
||||||
opsel, submit = (pm_pm4_opsel, pm_pm4_submit) if q.arg[1].startswith("COMPUTE") else (pm_sdma_opsel, pm_sdma_submit)
|
|
||||||
return submit.rewrite(graph_rewrite(q, opsel + pm_flatten_linear, walk=True, ctx=ctx, name=f"{q.arg[1]} opsel"), ctx)
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class AMDProgramData:
|
class AMDProgramData:
|
||||||
@@ -262,10 +240,10 @@ class AMDProgramData:
|
|||||||
private_segment_size:int; kernargs_segment_size:int; kernargs_alloc_size:int
|
private_segment_size:int; kernargs_segment_size:int; kernargs_alloc_size:int
|
||||||
enable_dispatch_ptr:int; enable_private_segment_sgpr:int
|
enable_dispatch_ptr:int; enable_private_segment_sgpr:int
|
||||||
|
|
||||||
_amd_program_cache:dict[tuple[bytes,str], tuple[AMDProgramData,bytes]] = {}
|
_amd_program_cache:dict[tuple[bytes, tuple[str, ...]], tuple[AMDProgramData, UOp]] = {}
|
||||||
def amd_build_program(prg:UOp) -> UOp:
|
def amd_build_program(dev, prg:UOp) -> tuple[AMDProgramData, UOp]:
|
||||||
dev = Device[to_tuple(prg.device)[0]] # TODO: rm this
|
# key on the full device tuple: the same lib can be built for different device sets, each needs its own program buffer
|
||||||
if (cached:=_amd_program_cache.get(key:=(lib:=prg.src[3].arg, dev.device))) is None:
|
if (cached:=_amd_program_cache.get(key:=(lib:=prg.src[3].arg, to_tuple(prg.device)))) is None:
|
||||||
image, sections, relocs = elf_loader(lib)
|
image, sections, relocs = elf_loader(lib)
|
||||||
rodata = next(sh.header.sh_addr for sh in sections if sh.name == ".rodata")
|
rodata = next(sh.header.sh_addr for sh in sections if sh.name == ".rodata")
|
||||||
for off, sym, typ, addent in relocs:
|
for off, sym, typ, addent in relocs:
|
||||||
@@ -282,20 +260,23 @@ def amd_build_program(prg:UOp) -> UOp:
|
|||||||
wave32=bool(desc.kernel_code_properties & 0x400), private_segment_size=desc.private_segment_fixed_size, kernargs_segment_size=desc.kernarg_size,
|
wave32=bool(desc.kernel_code_properties & 0x400), private_segment_size=desc.private_segment_fixed_size, kernargs_segment_size=desc.kernarg_size,
|
||||||
kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0), enable_dispatch_ptr=edp,
|
kernargs_alloc_size=desc.kernarg_size + (ctypes.sizeof(hsa.hsa_kernel_dispatch_packet_t) if edp else 0), enable_dispatch_ptr=edp,
|
||||||
enable_private_segment_sgpr=desc.kernel_code_properties & hsa.AMD_KERNEL_CODE_PROPERTIES_ENABLE_SGPR_PRIVATE_SEGMENT_BUFFER)
|
enable_private_segment_sgpr=desc.kernel_code_properties & hsa.AMD_KERNEL_CODE_PROPERTIES_ENABLE_SGPR_PRIVATE_SEGMENT_BUFFER)
|
||||||
|
image = bytes(image).ljust(round_up(len(image), 4), b"\x00") # the program is uploaded as whole dwords
|
||||||
buf = UOp.placeholder((len(image),), dtypes.uint8, next(UOp.unique_num), device=prg.device).rtag("program")
|
buf = UOp.placeholder((len(image),), dtypes.uint8, next(UOp.unique_num), device=prg.device).rtag("program")
|
||||||
cached = _amd_program_cache[key] = prg.replace(src=(buf.after(make_binary_patch(buf, bytes(image))),), arg=(data, prg.arg))
|
cached = _amd_program_cache[key] = (data, buf.after(buf.store(UOp(Ops.BINARY, src=(), arg=image).bitcast(buf.dtype))))
|
||||||
return cached
|
return cached
|
||||||
|
|
||||||
class AMDAllocator(HCQAllocator['AMDDevice']):
|
class AMDAllocator(HCQAllocator['AMDDevice']):
|
||||||
def __init__(self, dev:AMDDevice):
|
def __init__(self, dev:AMDDevice):
|
||||||
super().__init__(dev, supports_copy_from_disk=dev.has_sdma_queue, supports_transfer=dev.has_sdma_queue and not dev.is_usb())
|
super().__init__(dev, supports_copy_from_disk=dev.has_copy_queue, supports_transfer=dev.has_copy_queue and not dev.is_usb)
|
||||||
|
|
||||||
def _alloc(self, size:int, options:BufferSpec) -> HCQ2Buffer:
|
def _alloc(self, size:int, options:BufferSpec) -> HCQBuffer:
|
||||||
return self.dev.iface.alloc(size, host=options.host, uncached=options.uncached, cpu_access=options.cpu_access or not self.dev.has_sdma_queue)
|
return self.dev.iface.alloc(size, host=options.host, uncached=options.uncached, cpu_access=options.cpu_access or not self.dev.has_copy_queue)
|
||||||
|
|
||||||
def _do_free(self, opaque, options:BufferSpec): self.dev.iface.free(opaque)
|
def _do_free(self, opaque, options:BufferSpec): self.dev.iface.free(opaque)
|
||||||
|
|
||||||
def _do_map(self, buf:HCQ2Buffer): return self.dev.iface.map(buf._base if buf._base is not None else buf)
|
def _do_map(self, buf:HCQBuffer): return self.dev.iface.map(buf._base if buf._base is not None else buf)
|
||||||
|
|
||||||
|
def _do_unmap(self, buf:HCQBuffer): self.dev.iface.unmap(buf)
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AMDQueueDesc:
|
class AMDQueueDesc:
|
||||||
@@ -388,15 +369,24 @@ class KFDIface:
|
|||||||
return hcqbuf
|
return hcqbuf
|
||||||
|
|
||||||
def free(self, mem):
|
def free(self, mem):
|
||||||
|
self._unmap(mem)
|
||||||
|
if mem.va_addr: FileIOInterface.munmap(mem.va_addr, mem.size)
|
||||||
|
kfd.AMDKFD_IOC_FREE_MEMORY_OF_GPU(self.kfd, handle=mem.meta.handle)
|
||||||
|
|
||||||
|
def unmap(self, mem):
|
||||||
|
self._unmap(mem)
|
||||||
|
if getattr(mem, '_owns_kfd_handle', False): kfd.AMDKFD_IOC_FREE_MEMORY_OF_GPU(self.kfd, handle=mem.meta.handle)
|
||||||
|
|
||||||
|
def _unmap(self, mem):
|
||||||
gpus = (ctypes.c_int32 * 1)(self.gpu_id)
|
gpus = (ctypes.c_int32 * 1)(self.gpu_id)
|
||||||
stm = kfd.AMDKFD_IOC_UNMAP_MEMORY_FROM_GPU(self.kfd, handle=mem.meta.handle, device_ids_array_ptr=ctypes.addressof(gpus), n_devices=1)
|
stm = kfd.AMDKFD_IOC_UNMAP_MEMORY_FROM_GPU(self.kfd, handle=mem.meta.handle, device_ids_array_ptr=ctypes.addressof(gpus), n_devices=1)
|
||||||
assert stm.n_success == 1
|
assert stm.n_success == 1
|
||||||
if mem.owner == self.dev:
|
|
||||||
if mem.va_addr: FileIOInterface.munmap(mem.va_addr, mem.size)
|
|
||||||
kfd.AMDKFD_IOC_FREE_MEMORY_OF_GPU(self.kfd, handle=mem.meta.handle)
|
|
||||||
|
|
||||||
def map(self, mem):
|
def map(self, mem):
|
||||||
if mem.owner is not None and mem.owner._is_cpu(): return self.alloc(mem.size, host=True, cpu_addr=mem.va_addr)
|
if mem.owner is not None and mem.owner._is_cpu():
|
||||||
|
mapped = self.alloc(mem.size, host=True, cpu_addr=mem.va_addr)
|
||||||
|
mapped._owns_kfd_handle = True
|
||||||
|
return mapped
|
||||||
|
|
||||||
c_gpus = (ctypes.c_int32 * 1)(self.gpu_id)
|
c_gpus = (ctypes.c_int32 * 1)(self.gpu_id)
|
||||||
stm = kfd.AMDKFD_IOC_MAP_MEMORY_TO_GPU(self.kfd, handle=mem.meta.handle, device_ids_array_ptr=ctypes.addressof(c_gpus), n_devices=1)
|
stm = kfd.AMDKFD_IOC_MAP_MEMORY_TO_GPU(self.kfd, handle=mem.meta.handle, device_ids_array_ptr=ctypes.addressof(c_gpus), n_devices=1)
|
||||||
@@ -468,6 +458,7 @@ class PCIIface(PCIIfaceBase):
|
|||||||
|
|
||||||
def require_profile_mode(self): return True
|
def require_profile_mode(self): return True
|
||||||
def is_wgp_active(self, xcc, se, sa, wgp) -> bool: return True # TODO: account for WGP disablement on some asics.
|
def is_wgp_active(self, xcc, se, sa, wgp) -> bool: return True # TODO: account for WGP disablement on some asics.
|
||||||
|
def unmap(self, mem): self.free(mem)
|
||||||
|
|
||||||
def _compute_props(self):
|
def _compute_props(self):
|
||||||
self.ip_versions = self.dev_impl.ip_ver
|
self.ip_versions = self.dev_impl.ip_ver
|
||||||
@@ -512,8 +503,7 @@ class PCIIface(PCIIfaceBase):
|
|||||||
cq = d.compute_queue
|
cq = d.compute_queue
|
||||||
for b in (cq.put_value, cq.read_ptr, cq.write_ptr): b._buf.view.view(fmt='Q')[0] = 0
|
for b in (cq.put_value, cq.read_ptr, cq.write_ptr): b._buf.view.view(fmt='Q')[0] = 0
|
||||||
d.iface.dev_impl.gfx.setup_ring(*cq.params)
|
d.iface.dev_impl.gfx.setup_ring(*cq.params)
|
||||||
d.signal('timeline')._buf.cpu_view().mv.cast('Q')[0] = \
|
d.signal('timeline')._buf.cpu_view().view(fmt='Q')[0] = d.signal('value', 1, device="CPU")._buf.cpu_view().view(fmt='Q')[0] - 1
|
||||||
d.signal('value', 1).as_memoryview(force_zero_copy=True, no_sync=True).cast('Q')[0] - 1
|
|
||||||
|
|
||||||
def sleep(self, timeout):
|
def sleep(self, timeout):
|
||||||
if hasattr(self.pci_dev, 'irq_poller') and self.pci_dev.irq_poller is not None and (events_cnt:=len(self.pci_dev.irq_poller.poll(timeout))):
|
if hasattr(self.pci_dev, 'irq_poller') and self.pci_dev.irq_poller is not None and (events_cnt:=len(self.pci_dev.irq_poller.poll(timeout))):
|
||||||
@@ -527,31 +517,50 @@ class PCIIface(PCIIfaceBase):
|
|||||||
|
|
||||||
def device_fini(self): self.dev_impl.fini()
|
def device_fini(self): self.dev_impl.fini()
|
||||||
|
|
||||||
|
class USBIface(PCIIface):
|
||||||
|
def __init__(self, dev, dev_id): # pylint: disable=super-init-not-called
|
||||||
|
if dev_id >= len(visible:=hcq_filter_visible_devices(USB3.list_devices(0xADD1, 0x0001) + USB3.list_devices(0x3801, 0x0001), "AMD")):
|
||||||
|
raise RuntimeError(f"AMD:{dev_id} does not exist ({pluralize('device', len(visible))} available)")
|
||||||
|
self.dev, self.pci_dev, self.vram_bar, self.count = dev, USBPCIDevice("AM", *visible[dev_id]), 0, len(visible)
|
||||||
|
self.dev_impl = AMDev(self.pci_dev)
|
||||||
|
self._compute_props()
|
||||||
|
self.sram = self._dma_region(ctrl_addr=0xf000, sys_addr=0x200000, size=0x80000)
|
||||||
|
self.cq_buf = self._dma_region(ctrl_addr=0xb800, sys_addr=0x822000, size=0x1000) # +12 is the dword that releases an armed read
|
||||||
|
self.usb_handle = unwrap(ctypes.cast(self.pci_dev.usb.usb.handle, ctypes.c_void_p).value)
|
||||||
|
|
||||||
|
def _dma_region(self, ctrl_addr, sys_addr, size):
|
||||||
|
region = self.dev_impl.mm.map_range(vaddr:=self.dev_impl.mm.alloc_vaddr(size=size), size, [(sys_addr, size)], aspace=AddrSpace.SYS, uncached=True)
|
||||||
|
return HCQBuffer(vaddr, size, meta=PCIAllocationMeta(region, has_cpu_mapping=False), view=self.pci_dev.dma_view(ctrl_addr, size), owner=self.dev)
|
||||||
|
|
||||||
|
def alloc(self, size:int, host=False, uncached=False, cpu_access=False, contiguous=False, force_devmem=False, **kwargs) -> HCQBuffer:
|
||||||
|
# everything, even host-style signals, lives in vram: gpu writes into the bridge's own memory collide with an armed 0xF2 read stream
|
||||||
|
return super().alloc(size, host=False, uncached=uncached, cpu_access=cpu_access or host, contiguous=contiguous, force_devmem=True, **kwargs)
|
||||||
|
|
||||||
|
def sleep(self, timeout): pass
|
||||||
|
|
||||||
|
# we don't own the sram region, so the buffer never frees it
|
||||||
|
@functools.cached_property
|
||||||
|
def usb_sram(self) -> Buffer:
|
||||||
|
return Buffer(self.dev.device, (b:=self.sram).size, dtypes.uint8, options=BufferSpec(external_ptr=b.va_addr, nolru=True)).allocate(opaque=b)
|
||||||
|
|
||||||
def _mock(iface, name=None): return type(name or f"MOCK{iface.__name__}", (iface,), {})
|
def _mock(iface, name=None): return type(name or f"MOCK{iface.__name__}", (iface,), {})
|
||||||
|
|
||||||
class AMDDevice(HCQ2Compiled):
|
class AMDDevice(HCQ2Compiled):
|
||||||
pm_lower = PatternMatcher([
|
|
||||||
# prep program
|
|
||||||
(UPat(Ops.PROGRAM, src=(UPat(), UPat(), UPat(), UPat(Ops.BINARY)), name="prg"), amd_build_program),
|
|
||||||
|
|
||||||
# encoding of cmdbuf
|
|
||||||
(UPat(Ops.CUSTOM_FUNCTION, arg="submit_cmdbuf", src=(UPat(Ops.LINEAR, name="q"),)), encode_queue),
|
|
||||||
])
|
|
||||||
|
|
||||||
timestamp_divider = 100.0 # AMD GPU clock: ticks/us
|
timestamp_divider = 100.0 # AMD GPU clock: ticks/us
|
||||||
max_scratch_psize = 0
|
max_scratch_psize = 0
|
||||||
|
pm_encode = {"COMPUTE": pm_pm4_encode, "COPY": pm_sdma_encode}
|
||||||
|
pm_lower = {"COMPUTE": pm_pm4_submit, "COPY": pm_sdma_submit}
|
||||||
|
|
||||||
ifaces = [KFDIface, PCIIface, _mock(KFDIface, "MOCKIface"), _mock(KFDIface), _mock(PCIIface)]
|
ifaces = [KFDIface, PCIIface, USBIface, _mock(KFDIface, "MOCKIface"), _mock(KFDIface), _mock(PCIIface), _mock(USBIface)]
|
||||||
|
|
||||||
def device_props(self): return self.iface.props
|
def device_props(self): return self.iface.props
|
||||||
|
|
||||||
def is_am(self) -> bool: return isinstance(self.iface, (PCIIface,))
|
def is_am(self) -> bool: return isinstance(self.iface, (PCIIface,))
|
||||||
def is_usb(self) -> bool: return False
|
|
||||||
|
|
||||||
def __init__(self, device:str=""):
|
def __init__(self, device:str=""):
|
||||||
self.device_id = int(device.split(":")[1]) if ":" in device else 0
|
self.iface = self._select_iface(device)
|
||||||
|
self.is_usb = isinstance(self.iface, USBIface)
|
||||||
self.iface = self._select_iface()
|
if self.is_usb: self.rt_nbytes = 4 << 20
|
||||||
|
|
||||||
self.target:tuple[int, ...] = ((trgt:=self.iface.props['gfx_target_version']) // 10000, (trgt // 100) % 100, trgt % 100)
|
self.target:tuple[int, ...] = ((trgt:=self.iface.props['gfx_target_version']) // 10000, (trgt // 100) % 100, trgt % 100)
|
||||||
self.arch = "gfx%d%x%x" % self.target
|
self.arch = "gfx%d%x%x" % self.target
|
||||||
@@ -576,12 +585,12 @@ class AMDDevice(HCQ2Compiled):
|
|||||||
|
|
||||||
self.is_aql = getenv("AMD_AQL", int(self.xccs > 1))
|
self.is_aql = getenv("AMD_AQL", int(self.xccs > 1))
|
||||||
if self.is_aql:
|
if self.is_aql:
|
||||||
self.pm4_ibs = self.iface.alloc(0x2000 if self.is_usb() else (16 << 20), uncached=True, cpu_access=True)
|
self.pm4_ibs = self.iface.alloc(0x2000 if self.is_usb else (16 << 20), uncached=True, cpu_access=True)
|
||||||
self.pm4_ib_alloc = BumpAllocator(self.pm4_ibs.size, wrap=True)
|
self.pm4_ib_alloc = BumpAllocator(self.pm4_ibs.size, wrap=True)
|
||||||
|
|
||||||
self.max_copy_size = 0x40000000 if self.iface.ip_versions[am.SDMA0_HWIP][0] >= 5 else 0x400000
|
self.max_copy_size = 0x40000000 if self.iface.ip_versions[am.SDMA0_HWIP][0] >= 5 else 0x400000
|
||||||
self.sdma_queues:dict = {}
|
self.sdma_queues:dict = {}
|
||||||
self.has_sdma_queue = True # self.sdma_queue(0) is not None, TODO: think of this
|
self.has_copy_queue = not getenv("AMD_DISABLE_SDMA")
|
||||||
|
|
||||||
super().__init__(device, AMDAllocator(self), [HIPRenderer, AMDLLVMRenderer, HIPCCRenderer], None, can_recover=self.is_am(), arch=self.arch)
|
super().__init__(device, AMDAllocator(self), [HIPRenderer, AMDLLVMRenderer, HIPCCRenderer], None, can_recover=self.is_am(), arch=self.arch)
|
||||||
|
|
||||||
@@ -589,6 +598,10 @@ class AMDDevice(HCQ2Compiled):
|
|||||||
self.max_private_segment_size = 0
|
self.max_private_segment_size = 0
|
||||||
self.pm_bufferize = PatternMatcher([(UPat(Ops.PARAM, tag="scratch", name="b"), lambda ctx, b: ctx[0].scratch_buffer(b.max_numel()))]) + self.pm_bufferize
|
self.pm_bufferize = PatternMatcher([(UPat(Ops.PARAM, tag="scratch", name="b"), lambda ctx, b: ctx[0].scratch_buffer(b.max_numel()))]) + self.pm_bufferize
|
||||||
|
|
||||||
|
if self.is_usb:
|
||||||
|
self.pm_bufferize = pm_usb_bufferize + self.pm_bufferize
|
||||||
|
raise NotImplementedError("usb amd is not migrated to sealed submits yet") # a usb pm_lower can override the whole submit graph
|
||||||
|
|
||||||
self.pmc_enabled:bool = PROFILE > 0 and PMC > 0
|
self.pmc_enabled:bool = PROFILE > 0 and PMC > 0
|
||||||
if self.pmc_enabled:
|
if self.pmc_enabled:
|
||||||
self.iface.require_profile_mode()
|
self.iface.require_profile_mode()
|
||||||
@@ -649,7 +662,7 @@ class AMDDevice(HCQ2Compiled):
|
|||||||
wg_data_size = round_up((vgpr_size_per_cu + sgrp_size_per_cu + lds_size_per_cu + hwreg_size_per_cu) * self.cu_cnt, mmap.PAGESIZE)
|
wg_data_size = round_up((vgpr_size_per_cu + sgrp_size_per_cu + lds_size_per_cu + hwreg_size_per_cu) * self.cu_cnt, mmap.PAGESIZE)
|
||||||
ctl_stack_size = round_up((12 if self.target[0] != 9 else 8) * self.wave_cnt + 8 + 40, mmap.PAGESIZE)
|
ctl_stack_size = round_up((12 if self.target[0] != 9 else 8) * self.wave_cnt + 8 + 40, mmap.PAGESIZE)
|
||||||
return self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL if self.is_aql else kfd.KFD_IOC_QUEUE_TYPE_COMPUTE,
|
return self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_COMPUTE_AQL if self.is_aql else kfd.KFD_IOC_QUEUE_TYPE_COMPUTE,
|
||||||
0x2000 if self.is_usb() else (16 << 20), eop_buffer_size=0x1000,
|
0x2000 if self.is_usb else (16 << 20), eop_buffer_size=0x1000,
|
||||||
ctx_save_restore_size=0 if self.is_am() else wg_data_size + ctl_stack_size, ctl_stack_size=ctl_stack_size,
|
ctx_save_restore_size=0 if self.is_am() else wg_data_size + ctl_stack_size, ctl_stack_size=ctl_stack_size,
|
||||||
debug_memory_size=round_up(self.wave_cnt * 32, 64))
|
debug_memory_size=round_up(self.wave_cnt * 32, 64))
|
||||||
|
|
||||||
@@ -657,7 +670,7 @@ class AMDDevice(HCQ2Compiled):
|
|||||||
if getenv("AMD_DISABLE_SDMA"): return None
|
if getenv("AMD_DISABLE_SDMA"): return None
|
||||||
if idx in self.sdma_queues: return self.sdma_queues[idx]
|
if idx in self.sdma_queues: return self.sdma_queues[idx]
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
self.sdma_queues[idx] = self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_SDMA, 0x200 if self.is_usb() else (16 << 20), idx=idx)
|
self.sdma_queues[idx] = self.create_queue(kfd.KFD_IOC_QUEUE_TYPE_SDMA, 0x2000 if self.is_usb else (16 << 20), idx=idx)
|
||||||
return self.sdma_queues.get(idx, None)
|
return self.sdma_queues.get(idx, None)
|
||||||
|
|
||||||
def tmpring_size(self, private_segment_size):
|
def tmpring_size(self, private_segment_size):
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import functools, pathlib
|
import functools, pathlib
|
||||||
from dataclasses import replace
|
from tinygrad import Tensor
|
||||||
from tinygrad import Tensor, dtypes
|
|
||||||
from tinygrad.uop.ops import shape_to_shape_arg
|
|
||||||
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
from tinygrad.runtime.support.compiler_amd import HIPCCCompiler
|
||||||
|
|
||||||
FP8_MAX = 448.0
|
FP8_MAX = 448.0
|
||||||
@@ -12,7 +10,7 @@ NUM_WG, THREADS_PER_WG = 1024, 256
|
|||||||
@functools.cache
|
@functools.cache
|
||||||
def _local_abs_max_fxn(x_p, device):
|
def _local_abs_max_fxn(x_p, device):
|
||||||
x = Tensor(x_p, device=device)
|
x = Tensor(x_p, device=device)
|
||||||
inner = Tensor(x.uop.replace(src=(shape_to_shape_arg(x.uop.shard_shape),), arg=replace(x.uop.arg, axis=None))) if x.uop.axis is not None else x
|
inner = Tensor(x.uop.src[0]) if x.uop.axis is not None else x # the per-shard view of the flat param
|
||||||
return (inner.abs().max(),)
|
return (inner.abs().max(),)
|
||||||
|
|
||||||
def local_abs_max(x:Tensor) -> Tensor:
|
def local_abs_max(x:Tensor) -> Tensor:
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ def _custom_quantize_fp8_with_amax(fp8_out:UOp, amax_out:UOp, x:UOp, amax_state:
|
|||||||
else: raise NotImplementedError(f"no atomic max for device {device}")
|
else: raise NotImplementedError(f"no atomic max for device {device}")
|
||||||
amax_idx = amax_out.reshape((1,)).index(UOp.const(0))
|
amax_idx = amax_out.reshape((1,)).index(UOp.const(0))
|
||||||
max_val = lds[0].load()
|
max_val = lds[0].load()
|
||||||
atomic = UOp(Ops.CUSTOM, dtypes.void, (amax_idx, max_val.bitcast(dtypes.int32), max_val, amax_idx.load()), arg=atomic_arg)
|
atomic = UOp(Ops.CUSTOM, src=(amax_idx, max_val.bitcast(dtypes.int32), max_val, amax_idx.load()), arg=(atomic_arg, dtypes.void))
|
||||||
return atomic.end(tid, wg).sink(arg=KernelInfo(f"quantize_fp8_with_amax_{n_elems}", opts_to_apply=()))
|
return atomic.end(tid, wg).sink(arg=KernelInfo(f"quantize_fp8_with_amax_{n_elems}", opts_to_apply=()))
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ def _custom_quantize_mxfp4(row_fp4:UOp, row_scale:UOp, col_fp4:UOp, col_scale:UO
|
|||||||
mem = M*N*2 + M*N + M*N//16 # read bf16, write row+col fp4 + e8m0
|
mem = M*N*2 + M*N + M*N//16 # read bf16, write row+col fp4 + e8m0
|
||||||
outputs = (row_fp4, row_scale, col_fp4, col_scale)
|
outputs = (row_fp4, row_scale, col_fp4, col_scale)
|
||||||
sink = UOp.sink(*(o.base for o in outputs), x.base,
|
sink = UOp.sink(*(o.base for o in outputs), x.base,
|
||||||
*(UOp(Ops.CUSTOM, dtypes.void, (o.base.index(0),), arg="") for o in outputs),
|
*(UOp(Ops.CUSTOM, src=(o.base.index(0),), arg=("", dtypes.void)) for o in outputs),
|
||||||
UOp.special(256, "lidx0"), UOp.special(M//128, "gidx0"), UOp.special(N//64, "gidx1"),
|
UOp.special(256, "lidx0"), UOp.special(M//128, "gidx0"), UOp.special(N//64, "gidx1"),
|
||||||
arg=KernelInfo(name, estimates=Estimates(ops=12*M*N, mem=mem)))
|
arg=KernelInfo(name, estimates=Estimates(ops=12*M*N, mem=mem)))
|
||||||
src = (pathlib.Path(__file__).parent/"quantize_mxfp4.cpp").read_text()
|
src = (pathlib.Path(__file__).parent/"quantize_mxfp4.cpp").read_text()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import os
|
|||||||
# TODO: there is a timing bug without this
|
# TODO: there is a timing bug without this
|
||||||
os.environ["AMD_AQL"] = "1"
|
os.environ["AMD_AQL"] = "1"
|
||||||
|
|
||||||
from tinygrad import Tensor, Device, GlobalCounters, Context
|
from tinygrad import Tensor, Device, GlobalCounters, Context, dtypes
|
||||||
from tinygrad.helpers import getenv, DEV
|
from tinygrad.helpers import getenv, DEV
|
||||||
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
from tinygrad.uop.ops import UOp, Ops, KernelInfo
|
||||||
from tinygrad.renderer import Estimates
|
from tinygrad.renderer import Estimates
|
||||||
@@ -37,7 +37,7 @@ def launchBenchmark(instruction, vgprIndices, dense=True, accum=False, **kwargs)
|
|||||||
gidx = UOp.special(NUM_WORKGROUPS, "gidx0")
|
gidx = UOp.special(NUM_WORKGROUPS, "gidx0")
|
||||||
FLOPs = FLOPS_PER_MATMUL * NUM_WAVES * NUM_WORKGROUPS * INTERNAL_LOOP * INSTRUCTIONS_PER_LOOP
|
FLOPs = FLOPS_PER_MATMUL * NUM_WAVES * NUM_WORKGROUPS * INTERNAL_LOOP * INSTRUCTIONS_PER_LOOP
|
||||||
sink = UOp.sink(A.base, threads, gidx, arg=KernelInfo(inst.op.name.lower(), estimates=Estimates(ops=FLOPs, mem=0)))
|
sink = UOp.sink(A.base, threads, gidx, arg=KernelInfo(inst.op.name.lower(), estimates=Estimates(ops=FLOPs, mem=0)))
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts]))))
|
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=(x, dtypes.void)) for x in insts]))))
|
||||||
dummy = Tensor.zeros(1).contiguous().realize()
|
dummy = Tensor.zeros(1).contiguous().realize()
|
||||||
out = Tensor.custom_kernel(dummy, fxn=fxn)[0]
|
out = Tensor.custom_kernel(dummy, fxn=fxn)[0]
|
||||||
linear = out.schedule_linear()
|
linear = out.schedule_linear()
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ from tinygrad.helpers import getenv, DEBUG
|
|||||||
|
|
||||||
# https://github.com/facebookresearch/llama/blob/1076b9c51c77ad06e9d7ba8a4c6df775741732bd/llama/model.py#L47
|
# https://github.com/facebookresearch/llama/blob/1076b9c51c77ad06e9d7ba8a4c6df775741732bd/llama/model.py#L47
|
||||||
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
def precompute_freqs_cis(dim: int, end: int, theta: float = 10000.0) -> Tensor:
|
||||||
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2)[:(dim // 2)] / dim))
|
freqs = 1.0 / (theta ** (Tensor.arange(0, dim, 2, dtype=dtypes.float32)[:(dim // 2)] / dim))
|
||||||
freqs = Tensor.arange(end).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
freqs = Tensor.arange(end, dtype=dtypes.float32).unsqueeze(dim=1) * freqs.unsqueeze(dim=0)
|
||||||
return Tensor.stack(freqs.cos(), freqs.sin(), dim=-1).reshape(1, end, 1, dim//2, 2)
|
return Tensor.stack(freqs.cos(), freqs.sin(), dim=-1).cast(dtypes.default_float).reshape(1, end, 1, dim//2, 2)
|
||||||
|
|
||||||
# matches meta, non hugging face weights
|
# matches meta, non hugging face weights
|
||||||
# (a+i*b) * (c+i*d) = (ac-bd) + i*(ad+bc)
|
# (a+i*b) * (c+i*d) = (ac-bd) + i*(ad+bc)
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from tinygrad import Tensor
|
from tinygrad import Tensor
|
||||||
|
import os
|
||||||
from tinygrad.tensor import _to_np_dtype
|
from tinygrad.tensor import _to_np_dtype
|
||||||
from tinygrad.nn.onnx import OnnxRunner, OnnxValue
|
from tinygrad.nn.onnx import OnnxRunner, OnnxValue
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import onnxruntime as ort
|
import onnxruntime as ort
|
||||||
ort_options = ort.SessionOptions()
|
ort_options = ort.SessionOptions()
|
||||||
ort_options.log_severity_level = 3
|
ort_options.log_severity_level = 3
|
||||||
|
ort_options.intra_op_num_threads = os.cpu_count() or 1
|
||||||
|
|
||||||
def get_example_inputs(graph_inputs:dict[str, OnnxValue], config={}):
|
def get_example_inputs(graph_inputs:dict[str, OnnxValue], config={}):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -89,7 +89,8 @@ class TestBeamSearch(unittest.TestCase):
|
|||||||
s.apply_opt(Opt(OptOps.TC, 0, (-1, 0, 1)))
|
s.apply_opt(Opt(OptOps.TC, 0, (-1, 0, 1)))
|
||||||
up = prod([x for x, t in zip(s.full_shape, s.axis_types) if t in (AxisType.UPCAST, AxisType.UNROLL)])
|
up = prod([x for x, t in zip(s.full_shape, s.axis_types) if t in (AxisType.UPCAST, AxisType.UNROLL)])
|
||||||
actions = get_kernel_actions(s, include_0=False, max_up=int(up))
|
actions = get_kernel_actions(s, include_0=False, max_up=int(up))
|
||||||
upcasted = [s for s in actions.values() if any(opt.op in (OptOps.UPCAST, OptOps.UNROLL) for opt in s.applied_opts)]
|
upcasted = [s for s in actions.values() if any(o.op is OptOps.SPLIT and o.arg[1] in (AxisType.UPCAST, AxisType.UNROLL)
|
||||||
|
for o in s.applied_opts)]
|
||||||
assert len(upcasted) > 0, f"expected upcast/unroll actions after TC with max_up={up}, but got none"
|
assert len(upcasted) > 0, f"expected upcast/unroll actions after TC with max_up={up}, but got none"
|
||||||
|
|
||||||
def test_max_up(self):
|
def test_max_up(self):
|
||||||
@@ -98,8 +99,8 @@ class TestBeamSearch(unittest.TestCase):
|
|||||||
s = Scheduler(ast, Device[Device.DEFAULT].renderer)
|
s = Scheduler(ast, Device[Device.DEFAULT].renderer)
|
||||||
for max_up in (2, 4):
|
for max_up in (2, 4):
|
||||||
actions = get_kernel_actions(s, include_0=False, max_up=max_up)
|
actions = get_kernel_actions(s, include_0=False, max_up=max_up)
|
||||||
for up_opts in [s.applied_opts for s in actions.values() if any(opt.op in (OptOps.UPCAST, OptOps.UNROLL) for opt in s.applied_opts)]:
|
up_opts = [o for s in actions.values() for o in s.applied_opts if o.op is OptOps.SPLIT and o.arg[1] in (AxisType.UPCAST, AxisType.UNROLL)]
|
||||||
assert len([opt for opt in up_opts if opt.arg > max_up]) == 0 and len([op for op in up_opts if op.arg <= max_up]) > 0
|
assert len([opt for opt in up_opts if opt.arg[0] > max_up]) == 0 and len([op for op in up_opts if op.arg[0] <= max_up]) > 0
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Runbook: Llama 3 8B Training on DigitalOcean MI350X
|
||||||
|
|
||||||
|
## Machine Specs
|
||||||
|
- 8x MI350X GPUs (gfx950, device ID 75b0), 288GB VRAM each
|
||||||
|
- 2TB RAM, 192 CPUs, 2TB disk
|
||||||
|
- ROCm 7.14 at `/opt/rocm` (NOT `/opt/rocm-7.1.1` like the submission scripts assume)
|
||||||
|
- Python 3.12
|
||||||
|
|
||||||
|
## Phase 1: System Setup
|
||||||
|
|
||||||
|
### 1.1 Install packages
|
||||||
|
```bash
|
||||||
|
apt-get update
|
||||||
|
apt-get install -y python3-pip python3-venv git tmux rclone clang
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Install Python deps
|
||||||
|
```bash
|
||||||
|
python3 -m pip install --break-system-packages --ignore-installed typing-extensions numpy tqdm wandb tiktoken sentencepiece
|
||||||
|
```
|
||||||
|
Note: `--ignore-installed typing-extensions` is needed because the base image ships typing-extensions 4.10.0 without a RECORD file, so pip cannot uninstall it.
|
||||||
|
|
||||||
|
### 1.3 Install ROCm dev headers
|
||||||
|
The base image has ROCm runtime but NOT the HIP dev headers. Need:
|
||||||
|
```bash
|
||||||
|
apt-get install -y amdrocm-core-dev
|
||||||
|
```
|
||||||
|
This installs `hip/hip_runtime.h` at `/opt/rocm/core-7.14/include/hip/hip_runtime.h`.
|
||||||
|
The symlink `/opt/rocm/include` → `/opt/rocm/core-7.14/include` makes it available at `/opt/rocm/include/hip/hip_runtime.h`.
|
||||||
|
|
||||||
|
### 1.4 Configure ROCm comgr
|
||||||
|
ROCm 7.14 ships comgr 3.3 at `/opt/rocm/lib/libamd_comgr.so`. tinygrad's DLL loader needs explicit env vars to find it (it searches for `libcomgr.so*` by default, not `libamd_comgr.so*`). Set these in the run command:
|
||||||
|
```bash
|
||||||
|
export COMGR_PATH=/opt/rocm/lib/libamd_comgr.so
|
||||||
|
export COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so
|
||||||
|
```
|
||||||
|
Also add ROCm libs to ldconfig so comgr's shared library dependencies resolve:
|
||||||
|
```bash
|
||||||
|
cat > /etc/ld.so.conf.d/rocm.conf << 'EOF'
|
||||||
|
/opt/rocm/lib
|
||||||
|
/opt/rocm/lib/llvm/lib
|
||||||
|
/opt/rocm/lib/rocm_sysdeps/lib
|
||||||
|
EOF
|
||||||
|
ldconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 Install geohot tmux config
|
||||||
|
```bash
|
||||||
|
curl -sL https://raw.githubusercontent.com/geohot/configuration/master/.tmux.conf -o ~/.tmux.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.6 Verify GPU PCI access
|
||||||
|
The AM userspace driver accesses the GPUs directly over PCI. Do not load `amdgpu`. `/dev/kfd` is not required.
|
||||||
|
```bash
|
||||||
|
rmmod amdgpu
|
||||||
|
lspci -nnk -d 1002:
|
||||||
|
```
|
||||||
|
The MI350X devices should not show a `Kernel driver in use: amdgpu`.
|
||||||
|
|
||||||
|
## Phase 2: Clone tinygrad
|
||||||
|
```bash
|
||||||
|
cd /root
|
||||||
|
git clone https://github.com/tinygrad/tinygrad.git
|
||||||
|
cd tinygrad
|
||||||
|
python3 -m pip install --break-system-packages -e .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 3: Download C4 Dataset
|
||||||
|
|
||||||
|
The C4 data is on the MLCommons Cloudflare R2 bucket in Megatron-LM indexed format.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rclone config create mlc-training s3 provider=Cloudflare \
|
||||||
|
access_key_id=76ea42eadb867e854061a1806220ee1e \
|
||||||
|
secret_access_key=a53625c4d45e3ca8ac0df8a353ea3a41ffc3292aa25259addd8b7dc5a6ce2936 \
|
||||||
|
endpoint=c2686074cb2caf5cbaf6d134bdba8b47.r2.cloudflarestorage.com
|
||||||
|
|
||||||
|
mkdir -p /raid/datasets/c4-8b
|
||||||
|
(rclone copy mlc-training:mlcommons-training-wg-public/llama3_1/datasets/c4/llama3_1_8b/ /raid/datasets/c4-8b/ -P && \
|
||||||
|
PYTHONPATH=. python3 examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/buid_dataset_cache.py) \
|
||||||
|
> /root/dataset_cache.log 2>&1 &
|
||||||
|
```
|
||||||
|
Leave this running and proceed to the beam step while the dataset downloads and its cache builds.
|
||||||
|
|
||||||
|
### 3.1 Smoke test (beam search, 2 layers, fake data)
|
||||||
|
Always run beam first to validate the pipeline:
|
||||||
|
```bash
|
||||||
|
tmux new-session -d -s beam 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_beam.sh 2>&1 | tee /root/beam.log'
|
||||||
|
```
|
||||||
|
|
||||||
|
The beam test runs 10 training steps with 2 layers. Expected results:
|
||||||
|
- ~0.29s per step after warmup
|
||||||
|
- ~700K GFLOPS, ~7% MFU (low because only 2 layers)
|
||||||
|
- ~380 GB VRAM used
|
||||||
|
- Loss stable at ~12.55 with random init
|
||||||
|
|
||||||
|
Files downloaded (~85GB total, ~6 minutes):
|
||||||
|
- `c4-train.en_6_text_document.bin` (79 GB)
|
||||||
|
- `c4-train.en_6_text_document.idx` (870 MB)
|
||||||
|
- `c4-validation-91205-samples.en_text_document.bin` (159 MB)
|
||||||
|
- `c4-validation-91205-samples.en_text_document.idx` (1.8 MB)
|
||||||
|
- `LICENSE.txt`, `NOTICE.txt`
|
||||||
|
|
||||||
|
**Wait for rclone to fully complete before starting training.** Starting training while the dataset is still downloading will read a truncated .bin file, causing `ValueError: all input arrays must have the same shape` in the dataloader. The stale `.index_cache` and `.blend_cache` files must also be deleted if this happens:
|
||||||
|
```bash
|
||||||
|
rm -f /raid/datasets/c4-8b/*.index_cache /raid/datasets/c4-8b/*.blend_cache
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: wandb Login
|
||||||
|
```bash
|
||||||
|
wandb login
|
||||||
|
```
|
||||||
|
Enter API key from https://wandb.ai/authorize
|
||||||
|
|
||||||
|
Alternatively, pass the key directly:
|
||||||
|
```bash
|
||||||
|
wandb login <API_KEY>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 5: Run Training
|
||||||
|
|
||||||
|
Run training in tmux so it survives SSH disconnects:
|
||||||
|
```bash
|
||||||
|
tmux new-session -d -s train 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm WANDB=1 bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_run.sh 2>&1 | tee /root/train.log'
|
||||||
|
```
|
||||||
|
Attach with `tmux attach -t train`.
|
||||||
|
|
||||||
|
### 5.1 Full training run
|
||||||
|
```bash
|
||||||
|
tmux new-session -d -s train 'cd /root/tinygrad && COMGR_PATH=/opt/rocm/lib/libamd_comgr.so COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so CC=/opt/rocm/core-7.14/lib/llvm/bin/clang DEV=PCI+AMD:HIP ROCM_PATH=/opt/rocm WANDB=1 bash examples/mlperf/training_submission_v6.0/tinycorp/benchmarks/llama31_8b/implementations/tinybox_8xMI350X/dev_run.sh 2>&1 | tee /root/train.log'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variable Reference
|
||||||
|
|
||||||
|
| Variable | Value | Why |
|
||||||
|
|---|---|---|
|
||||||
|
| `COMGR_PATH` | `/opt/rocm/lib/libamd_comgr.so` | tinygrad's DLL loader needs explicit path to find comgr 3.3 |
|
||||||
|
| `COMGR_3_PATH` | `/opt/rocm/lib/libamd_comgr.so` | comgr 3.x uses a separate `comgr_3` module with its own path var |
|
||||||
|
| `CC` | `/opt/rocm/core-7.14/lib/llvm/bin/clang` | System clang doesn't know gfx950; must use ROCm's bundled clang |
|
||||||
|
| `DEV` | `PCI+AMD:HIP` | Force HIPRenderer (comgr-based) over HIPCCRenderer (hipcc subprocess) |
|
||||||
|
| `ROCM_PATH` | `/opt/rocm` | Script defaults to `/opt/rocm-7.1.1` which doesn't exist |
|
||||||
|
| `WANDB` | `1` | Enable wandb logging (off by default) |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
| Component | Source file |
|
||||||
|
|---|---|
|
||||||
|
| Model | `examples/mlperf/models/flat_llama.py` — FlatTransformer, FP8 MXFP4 weights, fused QKV, flash attention |
|
||||||
|
| Trainer | `examples/mlperf/model_train.py` → `train_llama3()` |
|
||||||
|
| Optimizer | `examples/mlperf/optim.py` — GradAccClipAdamW, master weights, FP8 re-quant |
|
||||||
|
| LR schedule | `examples/mlperf/lr_schedulers.py` — CosineAnnealingLRWithWarmup |
|
||||||
|
| Dataloader | `examples/mlperf/dataloader.py` — Megatron-LM indexed bin format |
|
||||||
|
| ASM GEMM | `extra/gemm/cdna_asm_gemm.py` — gfx950 MFMA assembly, MXFP4 |
|
||||||
|
| Flash attention | `extra/thunder/amd/fa.py` |
|
||||||
|
| Fused kernels | `extra/llama_kernels/` — rmsnorm, silu, quantize, fused_ce |
|
||||||
|
| GPU driver | `tinygrad/runtime/ops_amd.py` — HCQ, using the AM userspace PCI interface |
|
||||||
|
| Renderer | `tinygrad/renderer/cstyle.py` — HIPRenderer for gfx950 |
|
||||||
|
| comgr compiler | `tinygrad/runtime/support/compiler_amd.py` — HIPCompiler using comgr 3.3 |
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `'hip/hip_runtime.h' file not found`
|
||||||
|
Install `amdrocm-core-dev`:
|
||||||
|
```bash
|
||||||
|
apt-get install -y amdrocm-core-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### `'gfx950' is not a recognized processor` + LLVM crash
|
||||||
|
System clang doesn't know gfx950. Set `CC=/opt/rocm/core-7.14/lib/llvm/bin/clang`.
|
||||||
|
|
||||||
|
### `comgr not available: try setting COMGR_PATH?`
|
||||||
|
Add ROCm libs to ldconfig and set `COMGR_PATH` and `COMGR_3_PATH`:
|
||||||
|
```bash
|
||||||
|
# /etc/ld.so.conf.d/rocm.conf should contain /opt/rocm/lib paths
|
||||||
|
ldconfig
|
||||||
|
```
|
||||||
|
|
||||||
|
### `comgr not available: try setting COMGR_3_PATH?`
|
||||||
|
comgr 3.x uses a separate module. Set `COMGR_3_PATH=/opt/rocm/lib/libamd_comgr.so` too.
|
||||||
|
|
||||||
|
### `No such file or directory: 'clang'`
|
||||||
|
Install clang: `apt-get install -y clang` (for CPU compilation).
|
||||||
|
For gfx950 HIP compilation, comgr (not clang) is used — ensure the ROCm 7.14 comgr 3.3 is properly loaded via `COMGR_PATH` and `COMGR_3_PATH`.
|
||||||
|
|
||||||
|
## Appendix: KVM Virtualization Observations
|
||||||
|
|
||||||
|
### Virtualization detection
|
||||||
|
```
|
||||||
|
$ systemd-detect-virt
|
||||||
|
kvm
|
||||||
|
$ lspci -nn | grep AMD
|
||||||
|
83:00.0 ... Device [1002:75b0]
|
||||||
|
```
|
||||||
|
CPU flags include `hypervisor`. `dmesg` shows `Hypervisor detected: KVM`.
|
||||||
|
|
||||||
|
### No fan control
|
||||||
|
No `fan*` or `pwm*` hwmon entries exist. Only `temp*`, `power*`, `freq*` are exposed. GPU temps read 56-63°C, power ~265W per GPU.
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import os, subprocess, sys, shlex
|
import os, subprocess, sys, shlex, pickle
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tinygrad.helpers import temp, getenv
|
from tinygrad.helpers import temp, getenv
|
||||||
|
|
||||||
@@ -23,5 +23,8 @@ if __name__ == "__main__":
|
|||||||
# AM_RESET=1 gets a clear trace, does not work on mi300 machines
|
# AM_RESET=1 gets a clear trace, does not work on mi300 machines
|
||||||
subprocess.run([sys.executable, *shlex.split(test)], cwd=EXAMPLES_DIR.parent.parent.parent,
|
subprocess.run([sys.executable, *shlex.split(test)], cwd=EXAMPLES_DIR.parent.parent.parent,
|
||||||
env={**os.environ, "DEV":"AMD", "AM_RESET":"1" if not arch.startswith("gfx9") else "0", "VIZ":"-2", "PYTHONPATH":"."})
|
env={**os.environ, "DEV":"AMD", "AM_RESET":"1" if not arch.startswith("gfx9") else "0", "VIZ":"-2", "PYTHONPATH":"."})
|
||||||
|
with open(PROFILE_PATH, "rb") as f: events = pickle.load(f)
|
||||||
|
with open(PROFILE_PATH, "wb") as f:
|
||||||
|
pickle.dump([e for e in events if type(e).__name__ in {"ProfilePMCEvent", "ProfileSQTTEvent", "ProfileProgramEvent"}], f)
|
||||||
PROFILE_PATH.rename(dest:=EXAMPLES_DIR/arch/f"profile_{name}_run_{i}.pkl")
|
PROFILE_PATH.rename(dest:=EXAMPLES_DIR/arch/f"profile_{name}_run_{i}.pkl")
|
||||||
print(f"saved SQTT trace to {dest}")
|
print(f"saved SQTT trace to {dest}")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,13 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import ctypes, pathlib, argparse, pickle, dataclasses, threading, itertools
|
import ctypes, pathlib, argparse, pickle, dataclasses, threading, itertools
|
||||||
from decimal import Decimal
|
from typing import Any, Generator
|
||||||
from typing import Generator
|
|
||||||
from tinygrad.helpers import temp, unwrap, DEBUG
|
from tinygrad.helpers import temp, unwrap, DEBUG
|
||||||
from tinygrad.runtime.ops_amd import ProfileSQTTEvent
|
from tinygrad.runtime.ops_amd import ProfileSQTTEvent
|
||||||
from tinygrad.runtime.autogen import rocprof
|
from tinygrad.runtime.autogen import rocprof
|
||||||
from tinygrad.renderer.amd.dsl import Inst
|
from tinygrad.renderer.amd.dsl import Inst
|
||||||
from tinygrad.helpers import ProfileEvent, ProfileRangeEvent, ProfilePointEvent
|
from tinygrad.device import ProfileDeviceEvent, ProfileProgramEvent
|
||||||
from tinygrad.device import ProfileProgramEvent
|
|
||||||
from test.amd.disasm import disasm
|
from test.amd.disasm import disasm
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
@@ -39,17 +37,17 @@ class WaveExec(WaveSlot):
|
|||||||
insts_array = (struct*(len(self.insts)//sz)).from_buffer(self.insts)
|
insts_array = (struct*(len(self.insts)//sz)).from_buffer(self.insts)
|
||||||
for inst in insts_array:
|
for inst in insts_array:
|
||||||
inst_typ = rocprof.enum_rocprofiler_thread_trace_decoder_inst_category_t.get(inst.category)
|
inst_typ = rocprof.enum_rocprofiler_thread_trace_decoder_inst_category_t.get(inst.category)
|
||||||
yield InstExec(inst_typ, inst.pc.address, inst.stall, inst.duration, inst.time)
|
yield InstExec(inst_typ or "UNKNOWN", inst.pc.address, inst.stall, inst.duration, inst.time)
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class OccEvent(WaveSlot):
|
class OccEvent(WaveSlot):
|
||||||
time:int
|
time:int
|
||||||
start:int
|
start:int
|
||||||
|
|
||||||
RunKey = tuple[str, int]
|
RunKey = tuple[int, int]
|
||||||
|
|
||||||
class _ROCParseCtx:
|
class _ROCParseCtx:
|
||||||
def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]):
|
def __init__(self, sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]):
|
||||||
self.sqtt_evs, self.disasms = iter(sqtt_evs), {k:{k2:(disasm(v2), v2.size()) for k2,v2 in v.items()} for k,v in disasms.items()}
|
self.sqtt_evs, self.disasms = iter(sqtt_evs), {k:{k2:(disasm(v2), v2.size()) for k2,v2 in v.items()} for k,v in disasms.items()}
|
||||||
self.inst_execs:dict[RunKey, list[WaveExec]] = {}
|
self.inst_execs:dict[RunKey, list[WaveExec]] = {}
|
||||||
self.occ_events:dict[RunKey, list[OccEvent]] = {}
|
self.occ_events:dict[RunKey, list[OccEvent]] = {}
|
||||||
@@ -76,7 +74,7 @@ class _ROCParseCtx:
|
|||||||
self.inst_execs.setdefault(unwrap(self.active_run), []).append(WaveExec(ev.wave_id, ev.cu, ev.simd, unwrap(self.active_se), ev.begin_time,
|
self.inst_execs.setdefault(unwrap(self.active_run), []).append(WaveExec(ev.wave_id, ev.cu, ev.simd, unwrap(self.active_se), ev.begin_time,
|
||||||
ev.end_time, insts_blob))
|
ev.end_time, insts_blob))
|
||||||
|
|
||||||
def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]]) -> _ROCParseCtx:
|
def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[int, dict[int, Inst]]) -> _ROCParseCtx:
|
||||||
ROCParseCtx = _ROCParseCtx(sqtt_evs, disasms)
|
ROCParseCtx = _ROCParseCtx(sqtt_evs, disasms)
|
||||||
|
|
||||||
@rocprof.rocprof_trace_decoder_se_data_callback_t
|
@rocprof.rocprof_trace_decoder_se_data_callback_t
|
||||||
@@ -129,44 +127,7 @@ def decode(sqtt_evs:list[ProfileSQTTEvent], disasms:dict[str, dict[int, Inst]])
|
|||||||
raise exc
|
raise exc
|
||||||
return ROCParseCtx
|
return ROCParseCtx
|
||||||
|
|
||||||
def unpack_occ(viz_data, i:int, j:int, key:tuple[str, int], data:list, p:ProfileProgramEvent, target:str) -> dict:
|
def unpack_insts(w:WaveExec, pc_to_inst:dict[int, Inst]) -> dict:
|
||||||
from tinygrad.viz.serve import amd_decode, create_step, row_tuple
|
|
||||||
steps = viz_data.ctxs[i]["steps"]
|
|
||||||
if len(steps[j+1:]) > 0: return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]}
|
|
||||||
base = unwrap(p.base)
|
|
||||||
disasm:dict[int, Inst] = {addr+base:inst for addr,inst in amd_decode(unwrap(p.lib), target).items()}
|
|
||||||
rctx = decode(data, {p.tag:disasm})
|
|
||||||
cu_events:dict[str, list[ProfileEvent]] = {}
|
|
||||||
# ** inst traces
|
|
||||||
wave_insts:dict[str, dict[str, dict]] = {}
|
|
||||||
inst_units:dict[str, itertools.count] = {}
|
|
||||||
for w in rctx.inst_execs.get(key, []):
|
|
||||||
if (u:=w.wave_loc) not in inst_units: inst_units[u] = itertools.count(0)
|
|
||||||
n = next(inst_units[u])
|
|
||||||
if (events:=cu_events.get(w.cu_loc)) is None: cu_events[w.cu_loc] = events = []
|
|
||||||
events.append(ProfileRangeEvent(f"SIMD:{w.simd}", loc:=f"INST WAVE:{w.wave_id} N:{n}", Decimal(w.begin_time), Decimal(w.end_time)))
|
|
||||||
wave_insts.setdefault(w.cu_loc, {})[f"{u} N:{n}"] = {"wave":w, "disasm":disasm, "prg":p, "run_number":n, "loc":loc}
|
|
||||||
# ** occ traces (only WAVESTART/WAVEEND)
|
|
||||||
units:dict[str, itertools.count] = {}
|
|
||||||
wave_start:dict[str, int] = {}
|
|
||||||
for occ in rctx.occ_events.get(key, []):
|
|
||||||
if (u:=occ.wave_loc) not in units: units[u] = itertools.count(0)
|
|
||||||
if u in inst_units: continue
|
|
||||||
if occ.start: wave_start[u] = occ.time
|
|
||||||
else:
|
|
||||||
if (events:=cu_events.get(occ.cu_loc)) is None: cu_events[occ.cu_loc] = events = []
|
|
||||||
events.append(ProfileRangeEvent(f"SIMD:{occ.simd}", f"OCC WAVE:{occ.wave_id} N:{next(units[u])}", Decimal(wave_start.pop(u)),Decimal(occ.time)))
|
|
||||||
# ** split graph by CU
|
|
||||||
for cu in sorted(cu_events, key=row_tuple):
|
|
||||||
steps.append(create_step(f"{cu} {len(cu_events[cu])}", ("/cu-sqtt", i, len(steps)), depth=1,
|
|
||||||
data=[ProfilePointEvent(unit, "start", unit, ts=Decimal(0)) for unit in units]+cu_events[cu]))
|
|
||||||
for k in sorted(wave_insts.get(cu, []), key=row_tuple):
|
|
||||||
wd = wave_insts[cu][k]
|
|
||||||
steps.append(create_step(k.replace(cu, ""), ("/amd-sqtt-insts", i, len(steps)), loc=wd["loc"], depth=2,
|
|
||||||
data={"fxn":unpack_insts, "args":(wd,)}))
|
|
||||||
return {"steps":[{k:v for k,v in s.items() if k != "data"} for s in steps[j+1:]]}
|
|
||||||
|
|
||||||
def unpack_insts(viz_data, i:int, j:int, data:dict) -> dict:
|
|
||||||
columns = ["PC", "Instruction", "Hits", "Cycles", "Stall", "Type"]
|
columns = ["PC", "Instruction", "Hits", "Cycles", "Stall", "Type"]
|
||||||
inst_columns = ["N", "Clk", "Idle", "Dur", "Stall"]
|
inst_columns = ["N", "Clk", "Idle", "Dur", "Stall"]
|
||||||
# Idle: The total time gap between the completion of previous instruction and the beginning of the current instruction.
|
# Idle: The total time gap between the completion of previous instruction and the beginning of the current instruction.
|
||||||
@@ -176,36 +137,24 @@ def unpack_insts(viz_data, i:int, j:int, data:dict) -> dict:
|
|||||||
# * Instruction cache miss
|
# * Instruction cache miss
|
||||||
# Stall: The total number of cycles the hardware pipe couldn't issue an instruction.
|
# Stall: The total number of cycles the hardware pipe couldn't issue an instruction.
|
||||||
# Duration: Total latency in cycles, defined as "Stall time + Issue time" for gfx9 or "Stall time + Execute time" for gfx10+.
|
# Duration: Total latency in cycles, defined as "Stall time + Issue time" for gfx9 or "Stall time + Execute time" for gfx10+.
|
||||||
prev_instr = (w:=data["wave"]).begin_time
|
prev_instr = w.begin_time
|
||||||
pc_to_inst = data["disasm"]
|
|
||||||
start_pc = None
|
start_pc = None
|
||||||
rows:dict[int, dict] = {}
|
rows:dict[int, dict[str, Any]] = {}
|
||||||
for pc, inst in pc_to_inst.items():
|
for pc, inst in pc_to_inst.items():
|
||||||
if start_pc is None: start_pc = pc
|
if start_pc is None: start_pc = pc
|
||||||
rows[pc] = {"pc":pc-start_pc, "inst":str(inst), "hit_count":0, "dur":0, "stall":0, "type":"", "hits":{"cols":inst_columns, "rows":[]}}
|
rows[pc] = {"pc":pc-start_pc, "inst":str(inst), "hit_count":0, "dur":0, "stall":0, "type":"", "hits":{"cols":inst_columns, "rows":[]}}
|
||||||
for e in w.unpack_insts():
|
for e in w.unpack_insts():
|
||||||
if not (inst:=rows[e.pc]).get("type"): inst["type"] = str(e.typ).split("_")[-1]
|
if not (row:=rows[e.pc]).get("type"): row["type"] = str(e.typ).split("_")[-1]
|
||||||
inst["hit_count"] += 1
|
row["hit_count"] += 1
|
||||||
inst["dur"] += e.dur
|
row["dur"] += e.dur
|
||||||
inst["stall"] += e.stall
|
row["stall"] += e.stall
|
||||||
inst["hits"]["rows"].append((inst["hit_count"]-1, e.time, max(0, e.time-prev_instr), e.dur, e.stall))
|
row["hits"]["rows"].append((row["hit_count"]-1, e.time, max(0, e.time-prev_instr), e.dur, e.stall))
|
||||||
prev_instr = max(prev_instr, e.time + e.dur)
|
prev_instr = max(prev_instr, e.time + e.dur)
|
||||||
summary = [{"label":"Total Cycles", "value":w.end_time-w.begin_time}, {"label":"SE", "value":w.se}, {"label":"CU", "value":w.cu},
|
return {"rows":[tuple(v.values()) for v in rows.values()], "cols":columns}
|
||||||
{"label":"SIMD", "value":w.simd}, {"label":"Wave ID", "value":w.wave_id}, {"label":"Run number", "value":data["run_number"]}]
|
|
||||||
return {"rows":[tuple(v.values()) for v in rows.values()], "cols":columns, "metadata":[summary], "ref":viz_data.ref_map.get(data["prg"].name)}
|
|
||||||
|
|
||||||
def print_data(data:dict) -> None:
|
|
||||||
from tabulate import tabulate
|
|
||||||
# plaintext
|
|
||||||
if "src" in data: print(data["src"])
|
|
||||||
# table format
|
|
||||||
elif "cols" in data:
|
|
||||||
print(tabulate([r[:len(data["cols"])] for r in data["rows"]], headers=data["cols"], tablefmt="github"))
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
import tinygrad.viz.serve as viz
|
from tabulate import tabulate
|
||||||
from tinygrad.uop.ops import RewriteTrace
|
from tinygrad.viz.serve import amd_decode
|
||||||
data = viz.VizData()
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--profile', type=pathlib.Path, metavar="PATH", help='Path to profile (optional file, default: latest profile)',
|
parser.add_argument('--profile', type=pathlib.Path, metavar="PATH", help='Path to profile (optional file, default: latest profile)',
|
||||||
@@ -216,26 +165,28 @@ def main() -> None:
|
|||||||
|
|
||||||
with args.profile.open("rb") as f: profile = pickle.load(f)
|
with args.profile.open("rb") as f: profile = pickle.load(f)
|
||||||
|
|
||||||
viz.get_profile(profile, data=data)
|
|
||||||
|
|
||||||
# List all kernels
|
# List all kernels
|
||||||
if args.kernel is None:
|
if args.kernel is None:
|
||||||
for c in data.ctxs:
|
for p in profile:
|
||||||
print(c["name"])
|
if isinstance(p, ProfileProgramEvent) and p.device.startswith("AMD"): print(p.name)
|
||||||
for s in c["steps"]: print(" "+s["name"])
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Find kernel trace
|
prg = next((p for p in profile if isinstance(p, ProfileProgramEvent) and p.name == args.kernel), None)
|
||||||
trace = next((c for c in data.ctxs if c["name"] == f"SQTT {args.kernel}"), None)
|
dev = next((p for p in profile if isinstance(p, ProfileDeviceEvent) and p.device == prg.device), None)
|
||||||
if not trace: raise RuntimeError(f"no matching trace for {args.kernel}")
|
assert prg is not None and dev is not None, "must have program binary and device props"
|
||||||
n = 0
|
target = f"gfx{dev.props['gfx_target_version']//1000}"
|
||||||
for s in trace["steps"]:
|
sqtt = [p for p in profile if isinstance(p, ProfileSQTTEvent) and p.kern == prg.tag]
|
||||||
if "PKTS" in s["name"]: continue
|
|
||||||
print(s["name"])
|
pc_to_inst = {addr+prg.base:inst for addr,inst in amd_decode(prg.lib, target).items()}
|
||||||
ret = viz.get_render(data, s["query"])
|
rctx = decode(sqtt, {prg.tag:pc_to_inst})
|
||||||
print_data(ret)
|
waves = sorted(itertools.chain.from_iterable(rctx.inst_execs.values()), key=lambda w:(w.se, w.cu, w.simd, w.wave_id, w.begin_time))
|
||||||
n += 1
|
if not waves: raise RuntimeError(f"no instruction traces for {args.kernel}")
|
||||||
if n > args.n: break
|
run_numbers:dict[str, itertools.count] = {}
|
||||||
|
for w in itertools.islice(waves, args.n):
|
||||||
|
if w.wave_loc not in run_numbers: run_numbers[w.wave_loc] = itertools.count()
|
||||||
|
print(f"{w.wave_loc} N:{next(run_numbers[w.wave_loc])} Total Cycles:{w.end_time-w.begin_time}")
|
||||||
|
table = unpack_insts(w, pc_to_inst)
|
||||||
|
print(tabulate([r[:len(table["cols"])] for r in table["rows"]], headers=table["cols"], tablefmt="github"))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -19,16 +19,33 @@ def _sharded_empty(shape:Tensor, ref:Tensor, axis:int|None, dtype:DTypeLike|None
|
|||||||
@functools.cache
|
@functools.cache
|
||||||
def custom_fused_qkv_rope_forward(q:UOp, k:UOp, v:UOp, xqkv:UOp, freqs_cis:UOp,
|
def custom_fused_qkv_rope_forward(q:UOp, k:UOp, v:UOp, xqkv:UOp, freqs_cis:UOp,
|
||||||
device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
|
device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
|
||||||
code = (pathlib.Path(__file__).parent / "fused_qkv_rope.cpp").read_text()
|
group_size = H // H_KV
|
||||||
threads = 256
|
q, k, v = q.reshape(B, N, H, D), k.reshape(B, N, H_KV, D), v.reshape(B, N, H_KV, D)
|
||||||
thread_idx = UOp.special(threads, "lidx0")
|
xqkv = xqkv.reshape(B, N, H_KV, group_size + 2, D)
|
||||||
block_idx_x, block_idx_y = UOp.special(B, "gidx0"), UOp.special(N, "gidx1")
|
b, n = UOp.range(B, 0), UOp.range(N, 1)
|
||||||
sink = UOp.sink(q.base, k.base, v.base, xqkv.base, freqs_cis.base, thread_idx, block_idx_x, block_idx_y,
|
pair = UOp.range(D // 2, 2)
|
||||||
arg=KernelInfo(name="fused_qkv_rope_forward"))
|
even = pair * 2
|
||||||
compile_args = ["-std=c++20", "-ffast-math", f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}",
|
c = freqs_cis[0, n, 0, pair, 0].cast(dtypes.float)
|
||||||
f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DTHREADS_PER_BLOCK={threads}"]
|
s = freqs_cis[0, n, 0, pair, 1].cast(dtypes.float)
|
||||||
lib = HIPCCCompiler(arch, compile_args).compile_cached(code)
|
ordered:UOp|None = None
|
||||||
return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
for kvh in range(H_KV):
|
||||||
|
q_out, k_out, v_out = (x.after(ordered) if ordered is not None else x for x in (q, k, v))
|
||||||
|
x_in = xqkv.after(ordered) if ordered is not None else xqkv
|
||||||
|
stores:list[UOp] = []
|
||||||
|
for rep in range(group_size):
|
||||||
|
a = x_in[b, n, kvh, rep, even].cast(dtypes.float)
|
||||||
|
bb = x_in[b, n, kvh, rep, even + 1].cast(dtypes.float)
|
||||||
|
h = kvh * group_size + rep
|
||||||
|
stores += [q_out[b, n, h, even].store((a * c - bb * s).cast(q.dtype)), q_out[b, n, h, even + 1].store((a * s + bb * c).cast(q.dtype))]
|
||||||
|
a = x_in[b, n, kvh, group_size, even].cast(dtypes.float)
|
||||||
|
bb = x_in[b, n, kvh, group_size, even + 1].cast(dtypes.float)
|
||||||
|
stores += [k_out[b, n, kvh, even].store((a * c - bb * s).cast(k.dtype)),
|
||||||
|
k_out[b, n, kvh, even + 1].store((a * s + bb * c).cast(k.dtype)),
|
||||||
|
v_out[b, n, kvh, even].store(x_in[b, n, kvh, group_size + 1, even]),
|
||||||
|
v_out[b, n, kvh, even + 1].store(x_in[b, n, kvh, group_size + 1, even + 1])]
|
||||||
|
ordered = UOp.group(*stores)
|
||||||
|
assert ordered is not None
|
||||||
|
return ordered.end(pair, n, b).sink(arg=KernelInfo(name="fused_qkv_rope_forward"))
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def custom_fused_qkv_rope_backward(dxqkv:UOp, dq:UOp, dk:UOp, dv:UOp, freqs_cis:UOp,
|
def custom_fused_qkv_rope_backward(dxqkv:UOp, dq:UOp, dk:UOp, dv:UOp, freqs_cis:UOp,
|
||||||
@@ -109,8 +126,7 @@ def fused_qkv_rope(xqkv:Tensor, freqs_cis:Tensor, n_heads:int, n_kv_heads:int, h
|
|||||||
def _sharded_empty_like(ref:Tensor, axis:int|None=None) -> Tensor:
|
def _sharded_empty_like(ref:Tensor, axis:int|None=None) -> Tensor:
|
||||||
return _sharded_empty(ref.shape, ref, axis)
|
return _sharded_empty(ref.shape, ref, axis)
|
||||||
|
|
||||||
@functools.cache
|
def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink, window=0):
|
||||||
def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink):
|
|
||||||
def grad(dou:UOp, ker:UOp) -> tuple:
|
def grad(dou:UOp, ker:UOp) -> tuple:
|
||||||
do = Tensor(dou, device=dou.device)
|
do = Tensor(dou, device=dou.device)
|
||||||
attn = Tensor(ker.src[1].after(ker), device=ker.src[1].device)
|
attn = Tensor(ker.src[1].after(ker), device=ker.src[1].device)
|
||||||
@@ -129,7 +145,7 @@ def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, sha
|
|||||||
delta_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
delta_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
||||||
delta_vec, dq = Tensor.custom_kernel(delta_vec, dq, attn, do, fxn=functools.partial(custom_fa_backward_pre, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D))[:2]
|
delta_vec, dq = Tensor.custom_kernel(delta_vec, dq, attn, do, fxn=functools.partial(custom_fa_backward_pre, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D))[:2]
|
||||||
|
|
||||||
dq, dk_partial, dv_partial = Tensor.custom_kernel(dq, dk_partial, dv_partial, do, xq, xk, xv, l_vec, delta_vec, fxn=functools.partial(custom_fa_backward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D))[:3]
|
dq, dk_partial, dv_partial = Tensor.custom_kernel(dq, dk_partial, dv_partial, do, xq, xk, xv, l_vec, delta_vec, fxn=functools.partial(custom_fa_backward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, window=window))[:3]
|
||||||
|
|
||||||
if D == 64:
|
if D == 64:
|
||||||
dq = dq.reshape(B, H, N//16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2).permute(0, 1, 2, 8, 9, 10, 11, 3, 4, 6, 7, 5, 12).reshape(B, H, N, D).transpose(1, 2)
|
dq = dq.reshape(B, H, N//16, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2).permute(0, 1, 2, 8, 9, 10, 11, 3, 4, 6, 7, 5, 12).reshape(B, H, N, D).transpose(1, 2)
|
||||||
@@ -149,7 +165,7 @@ def _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, sha
|
|||||||
return grad
|
return grad
|
||||||
|
|
||||||
# TODO: remove write_flat once scheduler can remove reshapes between custom_kernel. TestCustomKernel.test_simple_reshape
|
# TODO: remove write_flat once scheduler can remove reshapes between custom_kernel. TestCustomKernel.test_simple_reshape
|
||||||
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False, write_flat:bool=False, sinks:Tensor|None=None):
|
def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False, write_flat:bool=False, sinks:Tensor|None=None, window:int=0):
|
||||||
assert attn_mask is None, "attn_mask not supported"
|
assert attn_mask is None, "attn_mask not supported"
|
||||||
assert is_causal, "only causal attention supported"
|
assert is_causal, "only causal attention supported"
|
||||||
|
|
||||||
@@ -176,18 +192,18 @@ def flash_attention(xq, xk, xv, attn_mask:Tensor|None=None, is_causal:bool=False
|
|||||||
attn = _sharded_empty((B, N, H * D), xq, axis=shard_axis) if write_flat else _sharded_empty_like(xq, axis=shard_axis)
|
attn = _sharded_empty((B, N, H * D), xq, axis=shard_axis) if write_flat else _sharded_empty_like(xq, axis=shard_axis)
|
||||||
l_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
l_vec = _sharded_empty((B, H, 1, N), xq, dtype=dtypes.float32, axis=shard_axis_t)
|
||||||
|
|
||||||
grad = _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink)
|
grad = _fa_grad_fxn(B, H, N, D, H_local, H_KV_local, H_KV, B_local, shard_axis, shard_axis_t, single_device, arch, has_sink, window=window)
|
||||||
|
|
||||||
fwd_inputs = (attn, l_vec, xq, xk, xv) + ((sinks,) if has_sink else ())
|
fwd_inputs = (attn, l_vec, xq, xk, xv) + ((sinks,) if has_sink else ())
|
||||||
attn, l_vec = Tensor.custom_kernel(*fwd_inputs, fxn=functools.partial(custom_fa_forward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, has_sink=has_sink), grad_fxn=grad)[:2]
|
attn, l_vec = Tensor.custom_kernel(*fwd_inputs, fxn=functools.partial(custom_fa_forward, device=single_device, arch=arch, B=B_local, N=N, H=H_local, H_KV=H_KV_local, D=D, has_sink=has_sink, window=window), grad_fxn=grad)[:2]
|
||||||
|
|
||||||
return attn, attn, l_vec
|
return attn, attn, l_vec
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, sinks:UOp|None=None, *, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, has_sink:bool=True):
|
def custom_fa_forward(o:UOp, l_vec:UOp, q:UOp, k:UOp, v:UOp, sinks:UOp|None=None, *, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, has_sink:bool=True, window:int=0):
|
||||||
code = (pathlib.Path(__file__).parent / "fa_fwd_causal.cpp").read_text()
|
code = (pathlib.Path(__file__).parent / "fa_fwd_causal.cpp").read_text()
|
||||||
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
||||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DATTN_SINK={int(has_sink)}"]
|
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DATTN_SINK={int(has_sink)}", f"-DWINDOW={window}"]
|
||||||
|
|
||||||
Q_BLOCK_SIZE = 32
|
Q_BLOCK_SIZE = 32
|
||||||
NUM_WARPS = 8
|
NUM_WARPS = 8
|
||||||
@@ -247,10 +263,10 @@ def custom_fa_backward_pre(delta_vec:UOp, dq:UOp, o:UOp, do:UOp, device:str, arc
|
|||||||
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
src=(sink, UOp(Ops.LINEAR, src=(*sink.src, sink)), UOp(Ops.SOURCE, arg=code), UOp(Ops.BINARY, arg=lib)))
|
||||||
|
|
||||||
@functools.cache
|
@functools.cache
|
||||||
def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_vec:UOp, delta_vec:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int):
|
def custom_fa_backward(dq:UOp, dk:UOp, dv:UOp, do:UOp, q:UOp, k:UOp, v:UOp, l_vec:UOp, delta_vec:UOp, device:str, arch:str, B:int, N:int, H:int, H_KV:int, D:int, window:int=0):
|
||||||
code = (pathlib.Path(__file__).parent / "fa_bwd_causal.cpp").read_text()
|
code = (pathlib.Path(__file__).parent / "fa_bwd_causal.cpp").read_text()
|
||||||
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
compile_args = [f"-I{(pathlib.Path(__file__).parent / 'include').as_posix()}", "-std=c++20", "-DKITTENS_CDNA4", "-DHIP_ENABLE_WARP_SYNC_BUILTINS", "-ffast-math",
|
||||||
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}"]
|
f"-DATTN_B={B}", f"-DATTN_N={N}", f"-DATTN_H={H}", f"-DATTN_H_KV={H_KV}", f"-DATTN_D={D}", f"-DWINDOW={window}"]
|
||||||
|
|
||||||
BLOCK_SIZE_KV = 256
|
BLOCK_SIZE_KV = 256
|
||||||
GROUP_SIZE = H // H_KV
|
GROUP_SIZE = H // H_KV
|
||||||
|
|||||||
@@ -269,7 +269,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
|
|
||||||
qo_tile<D, float> q_reg_fl;
|
qo_tile<D, float> q_reg_fl;
|
||||||
load<1, qo_tile<D, float>, _gl_QKVO>(q_reg_fl, g.Qg, {batch_idx, tile_idx, head_idx, 0});
|
load<1, qo_tile<D, float>, _gl_QKVO>(q_reg_fl, g.Qg, {batch_idx, tile_idx, head_idx, 0});
|
||||||
|
#if !WINDOW
|
||||||
mul(q_reg_fl, q_reg_fl, TEMPERATURE_SCALE); // Use sqrtf for clarity
|
mul(q_reg_fl, q_reg_fl, TEMPERATURE_SCALE); // Use sqrtf for clarity
|
||||||
|
#endif
|
||||||
copy(q_reg, q_reg_fl);
|
copy(q_reg, q_reg_fl);
|
||||||
transpose(q_reg_transposed, q_reg);
|
transpose(q_reg_transposed, q_reg);
|
||||||
|
|
||||||
@@ -288,6 +290,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[0]);
|
zero(att_block[0]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
__builtin_amdgcn_sched_barrier(0);
|
__builtin_amdgcn_sched_barrier(0);
|
||||||
if constexpr (causal) {
|
if constexpr (causal) {
|
||||||
const int kv_end_pos = (min_tile + 1) * KV_BLOCK_SIZE;
|
const int kv_end_pos = (min_tile + 1) * KV_BLOCK_SIZE;
|
||||||
@@ -337,6 +342,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[1]);
|
zero(att_block[1]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
#if WINDOW
|
#if WINDOW
|
||||||
// window masks interior tiles that causal skips
|
// window masks interior tiles that causal skips
|
||||||
mask_kv_tile(att_block[1], tile_idx, j - 2, neg_inf_v, lane);
|
mask_kv_tile(att_block[1], tile_idx, j - 2, neg_inf_v, lane);
|
||||||
@@ -401,6 +409,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[0]);
|
zero(att_block[0]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
// Finish softmax for QK1
|
// Finish softmax for QK1
|
||||||
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
||||||
mul(norm_vec, norm_vec, scale_vec);
|
mul(norm_vec, norm_vec, scale_vec);
|
||||||
@@ -469,6 +480,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[1]);
|
zero(att_block[1]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
// Finish softmax for QK2
|
// Finish softmax for QK2
|
||||||
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
||||||
mul(norm_vec, norm_vec, scale_vec);
|
mul(norm_vec, norm_vec, scale_vec);
|
||||||
@@ -535,6 +549,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[0]);
|
zero(att_block[0]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
mma_AtB(att_block[0], k_reg_transposed, q_reg_transposed, att_block[0]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[0], att_block[0], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
// Finish softmax for QK3
|
// Finish softmax for QK3
|
||||||
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
exp2(att_block[1].tiles[1][0], att_block[1].tiles[1][0]);
|
||||||
mul(norm_vec, norm_vec, scale_vec);
|
mul(norm_vec, norm_vec, scale_vec);
|
||||||
@@ -597,6 +614,9 @@ __global__ void attend_ker(bf16 *O_ptr, float *L_vec_ptr, bf16 *Q_ptr, bf16 *K_p
|
|||||||
zero(att_block[1]);
|
zero(att_block[1]);
|
||||||
transpose(k_reg_transposed, k_reg);
|
transpose(k_reg_transposed, k_reg);
|
||||||
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
mma_AtB(att_block[1], k_reg_transposed, q_reg_transposed, att_block[1]);
|
||||||
|
#if WINDOW
|
||||||
|
mul(att_block[1], att_block[1], TEMPERATURE_SCALE);
|
||||||
|
#endif
|
||||||
// Finish softmax for QK4
|
// Finish softmax for QK4
|
||||||
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
exp2(att_block[0].tiles[1][0], att_block[0].tiles[1][0]);
|
||||||
mul(norm_vec, norm_vec, scale_vec);
|
mul(norm_vec, norm_vec, scale_vec);
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
#include <hip/hip_runtime.h>
|
|
||||||
#include <hip/hip_bf16.h>
|
|
||||||
|
|
||||||
#ifndef ATTN_B
|
|
||||||
#define ATTN_B 2
|
|
||||||
#endif
|
|
||||||
#ifndef ATTN_N
|
|
||||||
#define ATTN_N 8192
|
|
||||||
#endif
|
|
||||||
#ifndef ATTN_H
|
|
||||||
#define ATTN_H 32
|
|
||||||
#endif
|
|
||||||
#ifndef ATTN_H_KV
|
|
||||||
#define ATTN_H_KV 8
|
|
||||||
#endif
|
|
||||||
#ifndef ATTN_D
|
|
||||||
#define ATTN_D 128
|
|
||||||
#endif
|
|
||||||
#ifndef THREADS_PER_BLOCK
|
|
||||||
#define THREADS_PER_BLOCK 256
|
|
||||||
#endif
|
|
||||||
|
|
||||||
constexpr int GROUP_SIZE = ATTN_H / ATTN_H_KV;
|
|
||||||
constexpr int HALF_D = ATTN_D / 2;
|
|
||||||
constexpr int PACKED_D = (GROUP_SIZE + 2) * ATTN_D;
|
|
||||||
|
|
||||||
extern "C" __global__ __launch_bounds__(THREADS_PER_BLOCK) void
|
|
||||||
fused_qkv_rope_forward(
|
|
||||||
__hip_bfloat16* __restrict__ q,
|
|
||||||
__hip_bfloat16* __restrict__ k,
|
|
||||||
__hip_bfloat16* __restrict__ v,
|
|
||||||
const __hip_bfloat16* __restrict__ xqkv,
|
|
||||||
const __hip_bfloat16* __restrict__ freqs_cis) {
|
|
||||||
const int b = blockIdx.x;
|
|
||||||
const int n = blockIdx.y;
|
|
||||||
const int bn = b * ATTN_N + n;
|
|
||||||
const int packed_bn = bn * ATTN_H_KV * PACKED_D;
|
|
||||||
const int q_bn = bn * ATTN_H * ATTN_D;
|
|
||||||
const int kv_bn = bn * ATTN_H_KV * ATTN_D;
|
|
||||||
|
|
||||||
if (threadIdx.x < HALF_D) {
|
|
||||||
const int pair = threadIdx.x;
|
|
||||||
const int even = pair << 1;
|
|
||||||
const float c = static_cast<float>(freqs_cis[((n * HALF_D + pair) * 2) + 0]);
|
|
||||||
const float s = static_cast<float>(freqs_cis[((n * HALF_D + pair) * 2) + 1]);
|
|
||||||
|
|
||||||
for (int kvh = 0; kvh < ATTN_H_KV; kvh++) {
|
|
||||||
const int base = packed_bn + kvh * PACKED_D;
|
|
||||||
|
|
||||||
for (int rep = 0; rep < GROUP_SIZE; rep++) {
|
|
||||||
const int qbase = base + rep * ATTN_D;
|
|
||||||
const int h = kvh * GROUP_SIZE + rep;
|
|
||||||
const float a = static_cast<float>(xqkv[qbase + even]);
|
|
||||||
const float bb = static_cast<float>(xqkv[qbase + even + 1]);
|
|
||||||
const int out = q_bn + h * ATTN_D + even;
|
|
||||||
q[out] = static_cast<__hip_bfloat16>(a * c - bb * s);
|
|
||||||
q[out + 1] = static_cast<__hip_bfloat16>(a * s + bb * c);
|
|
||||||
}
|
|
||||||
|
|
||||||
const float a = static_cast<float>(xqkv[base + GROUP_SIZE * ATTN_D + even]);
|
|
||||||
const float bb = static_cast<float>(xqkv[base + GROUP_SIZE * ATTN_D + even + 1]);
|
|
||||||
const int out = kv_bn + kvh * ATTN_D + even;
|
|
||||||
k[out] = static_cast<__hip_bfloat16>(a * c - bb * s);
|
|
||||||
k[out + 1] = static_cast<__hip_bfloat16>(a * s + bb * c);
|
|
||||||
v[out] = xqkv[base + (GROUP_SIZE + 1) * ATTN_D + even];
|
|
||||||
v[out + 1] = xqkv[base + (GROUP_SIZE + 1) * ATTN_D + even + 1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -209,7 +209,7 @@ class ST:
|
|||||||
return cls(uop, rows, cols, layout, base_shape, ker)
|
return cls(uop, rows, cols, layout, base_shape, ker)
|
||||||
|
|
||||||
def swizzle(self, row, col):
|
def swizzle(self, row, col):
|
||||||
swizzled_offset = self.base_shape.swizzle(row, col, self._uop.dtype.scalar())
|
swizzled_offset = self.base_shape.swizzle(row, col, self._uop.dtype)
|
||||||
|
|
||||||
row = swizzled_offset // self.base_shape.cols
|
row = swizzled_offset // self.base_shape.cols
|
||||||
col = swizzled_offset % self.base_shape.cols
|
col = swizzled_offset % self.base_shape.cols
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
from tinygrad.tensor import Tensor
|
|
||||||
from tinygrad.helpers import CHUNK_SIZE
|
|
||||||
from tinygrad.nn.state import fs_load
|
|
||||||
import argparse, math, hashlib
|
|
||||||
|
|
||||||
def _python_hash_1mb(data:bytes|bytearray):
|
|
||||||
chunks = [data[i:i+4096] for i in range(0, len(data), 4096)]
|
|
||||||
chunk_hashes = [hashlib.shake_128(chunk).digest(16) for chunk in chunks]
|
|
||||||
return hashlib.shake_128(b''.join(chunk_hashes)).digest(16)
|
|
||||||
|
|
||||||
def hash_file(data: bytes|bytearray):
|
|
||||||
if len(data) % CHUNK_SIZE != 0: data += bytes(CHUNK_SIZE - len(data) % CHUNK_SIZE)
|
|
||||||
base_chunks = math.ceil(len(data) / CHUNK_SIZE)
|
|
||||||
tree_depth = math.ceil(math.log(base_chunks, CHUNK_SIZE // 16))
|
|
||||||
|
|
||||||
for _ in range(tree_depth + 1):
|
|
||||||
data_chunks = [data[i:i+CHUNK_SIZE] for i in range(0, len(data), CHUNK_SIZE)]
|
|
||||||
data_chunk_hashes = [_python_hash_1mb(chunk) for chunk in data_chunks]
|
|
||||||
data = b''.join(data_chunk_hashes)
|
|
||||||
if len(data) % CHUNK_SIZE != 0: data += bytes(CHUNK_SIZE - len(data) % CHUNK_SIZE)
|
|
||||||
|
|
||||||
return data[:16]
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument("--hash", type=str, required=True, help="file hash to fetch")
|
|
||||||
parser.add_argument("--len", type=int, required=True, help="file length to fetch")
|
|
||||||
parser.add_argument("--dest", type=str, required=True, help="destination path to save the file")
|
|
||||||
parser.add_argument("--check", action="store_true", help="verify the file hash after fetching")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
fs_load(Tensor(bytes.fromhex(args.hash), device="CPU"), args.len).to(f"disk:{args.dest}").realize()
|
|
||||||
|
|
||||||
if args.check:
|
|
||||||
with open(args.dest, "rb") as f:
|
|
||||||
data = f.read()
|
|
||||||
assert hash_file(data) == bytes.fromhex(args.hash), "Hash mismatch after fetching file"
|
|
||||||
print("File hash verified successfully!")
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
import json, multiprocessing, functools
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from tinygrad.tensor import Tensor
|
|
||||||
from tinygrad.helpers import tqdm, getenv
|
|
||||||
from tinygrad.nn.state import fs_load
|
|
||||||
|
|
||||||
raid_root = Path(getenv("RAID_ROOT", "/raid"))
|
|
||||||
|
|
||||||
def fetch_file(item):
|
|
||||||
path, info = item
|
|
||||||
h, size = info["hash"], info["size"]
|
|
||||||
|
|
||||||
path = raid_root / Path(path)
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
pt = fs_load(Tensor(bytes.fromhex(h), device="CPU"), size).to(f"disk:{path.as_posix()}").realize()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"error fetching {path}, {h}, {size}: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
pt.uop.buffer.deallocate()
|
|
||||||
|
|
||||||
def fetch_mapping(h, l):
|
|
||||||
mapping_tensor = fs_load(Tensor(bytes.fromhex(h)), l).realize()
|
|
||||||
mapping = mapping_tensor.data().tobytes().decode()
|
|
||||||
mapping = json.loads(mapping)
|
|
||||||
mapped_files = mapping.items()
|
|
||||||
return list(mapped_files)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
h, l = getenv("HASH", "d734f5e3be9f1e9d863bfaa4fc6c1ef2"), getenv("LENGTH", 175866113)
|
|
||||||
|
|
||||||
with multiprocessing.Pool(processes=1) as pool:
|
|
||||||
mapped_files = pool.apply(functools.partial(fetch_mapping, h, l))
|
|
||||||
|
|
||||||
print(f"fetched mapping for {len(mapped_files)} files")
|
|
||||||
|
|
||||||
with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
|
|
||||||
for _ in tqdm(pool.imap_unordered(fetch_file, mapped_files), total=len(mapped_files)):
|
|
||||||
pass
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
import multiprocessing, json
|
|
||||||
|
|
||||||
from tinygrad.tensor import Tensor
|
|
||||||
from tinygrad.helpers import tqdm
|
|
||||||
from tinygrad.nn.state import fs_store
|
|
||||||
|
|
||||||
raid_root = Path("/raid")
|
|
||||||
|
|
||||||
def upload_file(path: Path):
|
|
||||||
pt = Tensor(path).realize()
|
|
||||||
h = fs_store(pt).realize()
|
|
||||||
pt.uop.realized.deallocate()
|
|
||||||
return h.data().hex(), path, pt.nbytes()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
raid_files = sorted([p for p in raid_root.rglob("*") if p.is_file()])
|
|
||||||
print(f"found {len(raid_files)} files in /raid")
|
|
||||||
|
|
||||||
mapping = {}
|
|
||||||
with multiprocessing.Pool(processes=multiprocessing.cpu_count()) as pool:
|
|
||||||
for h, p, s in tqdm(pool.imap_unordered(upload_file, raid_files), total=len(raid_files)):
|
|
||||||
mapping[p.relative_to(raid_root).as_posix()] = {"hash": h, "size": s}
|
|
||||||
|
|
||||||
# sort the mapping by key
|
|
||||||
mapping = dict(sorted(mapping.items()))
|
|
||||||
|
|
||||||
mapping = json.dumps(mapping).encode()
|
|
||||||
mapping_tensor = Tensor(mapping, device="CPU")
|
|
||||||
h = fs_store(mapping_tensor).realize()
|
|
||||||
|
|
||||||
print(f"final hash: {h.data().hex()}, size: {len(mapping)}")
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user