Files
TheRock/rocm-libraries/projects/hipblaslt/tensilelite/Tensile/Tests/unit/test_ValidatePack.py
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1730 lines
68 KiB
Python

################################################################################
#
# Copyright (C) 2025 Advanced Micro Devices, Inc. All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# SPDX-License-Identifier: MIT
################################################################################
from typing import Any, Optional
import pytest
from rocisa.instruction import SWaitCnt, SNop, SBarrier
from Tensile.Components.CMSValidator import (
add_local_read_constraints, add_pack_constraints, isValid,
)
from Tensile.Components.CustomSchedule import ScheduleInfo
from cms_validation_base import CMSValidationTestBase
class TestValidatePackBF16(CMSValidationTestBase):
"""
Validate the Pack instructions present in BF16 kernels.
Here, the pack commands map to v_perm.
"""
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing(self):
"""
Passing case where pack instructions are issued after the SWaitCnt guaranteeing LRs are complete.
- 8 LRs per group (LRA0, LRB0)
- 8 Packs per group (PackA0, PackB0) - testing a subset of the 32 expected
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[2, 5]],
"LRA0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
"PackB1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_different_number_LRs(self):
"""
Same as above, except that there are more LRA0s and fewer LRB0s (than MIWave).
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[2, 5]],
"LRA0": [[0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
"PackB1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_fail_too_early(self):
"""
Failing case where pack instructions are issued before the SWaitCnt guaranteeing LRs are complete.
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[3, self.num_vmfma-1]],
"LRA0": [[0, 0, 0, 0, 1, 1, 1, 1]],
"LRB0": [[0, 0, 0, 0, 1, 1, 1, 1]],
"PackA0": [[2, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
"PackB1": [[-1, -1, -1, -1, -1, -1, -1, -1]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
# PackA0 at index 2 is invalid because LRs are only guaranteed at index 3 (after SWaitCnt)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=2 issued too early, must be issued after idx=3 (because of LRA0 issued @ idx=0).")
class TestValidatePackBF16MFMAReorder(CMSValidationTestBase):
"""
Validate the Pack instructions present in BF16 kernels.
Here, the pack commands map to v_perm.
Change from column-major to row-major.
1st half
| 0 2 | -> | 0 1 |
| 1 3 | -> | 2 3 |
2nd half
| 4 6 | -> | 4 5 |
| 5 7 | -> | 6 7 |
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
super().setup_method(method, kernel_updates=kernel_updates)
self.mfma_reorder = [0, 2, 1, 3, 4, 6, 5, 7]
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing(self):
"""
Passing case where pack instructions are issued after the SWaitCnt guaranteeing LRs are complete.
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[2]],
"LRA0": [[0, 0]],
"LRB0": [[0, 0]],
"PackA0": [[3, 3, 5, 5]],
"PackB0": [[3, 3, 4, 4]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None, mfmaReorder=self.mfma_reorder)
def test_fail_too_early(self):
"""
Failing case where pack instructions are issued before the SWaitCnt guaranteeing LRs are complete.
"""
assert self.num_vmfma == 8
optSchedule = {
"LRA0": [[-1, -1]],
"LRB0": [[-1, -1]],
"SYNC": [[3]],
"PackA0": [[2, 3, 5, 5]],
"PackB0": [[3, 3, 4, 4]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=2 issued too early, must be issued after idx=3 (because of LRA0 issued @ idx=-1).", mfmaReorder=self.mfma_reorder)
def test_failing_too_late(self):
"""
Failing case where pack instructions are issued after the MFMA that needs them.
With mfma_reorder = [0, 2, 1, 3, 4, 6, 5, 7]:
- Logical index 4 -> execution position 4
- Logical index 5 -> execution position 6
- Logical index 6 -> execution position 5
For PackA0 (2 tiles, 2 packs per tile):
- Tile 0: needed at logical 4 -> execution position 4
- Tile 1: needed at logical 5 -> execution position 6
If we issue PackA0 at position 4 or later, it's too late for tile 0.
"""
assert self.num_vmfma == 8
optSchedule = {
"LRA0": [[-1, -1]],
"LRB0": [[-1, -1]],
"SYNC": [[-1]],
"PackA0": [[3, 4, 5, 5]],
"PackB0": [[3, 3, 4, 4]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=4 issued too late, must be issued before MFMA @ idx=4.", mfmaReorder=self.mfma_reorder)
class TestValidatePackBF16PLRPack(CMSValidationTestBase):
"""
Validate the Pack instructions present in BF16 kernels with UsePLRPack.
Here, Pack commands map to v_perm.
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update({"UsePLRPack": 1})
super().setup_method(method, kernel_updates=kernel_updates)
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing_plr_pack(self):
"""
Passing case where pack instructions are issued after the SWaitCnt guaranteeing LRs are complete.
- 8 LRs per group (LRA0, LRB0)
- 8 Packs per group (PackA0, PackB0) - testing a subset of the 32 expected
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[2, 5]],
"LRA0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[6, 6, 6, 6, 6, 6, 6, 6]],
"PackB1": [[6, 6, 6, 6, 6, 6, 6, 6]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_plr_pack_different_number_LRs(self):
"""
Same as above, except that there are more LRA0s and fewer LRB0s (than MIWaveTileA).
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[2, 5]],
"LRA0": [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4]],
"PackA1": [[6, 6, 6, 6, 6, 6, 6, 6]],
"PackB1": [[6, 6, 6, 6, 6, 6, 6, 6]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_fail_too_early_plr_pack(self):
"""
Failing case where pack instructions are issued before the SWaitCnt guaranteeing LRs are complete.
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[3, 6]],
"LRA0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[5, 6, 6, 6, 6, 6, 6, 6]],
"PackB1": [[6, 6, 6, 6, 6, 6, 6, 6]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
# PackA1 at index 5 is invalid because LRs are only guaranteed at index 6 (after SWaitCnt)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA1 @ idx=5 issued too early, must be issued after idx=6 (because of LRA1 issued @ idx=4).")
def test_fail_too_early_more_lrs_plr_pack(self):
"""
Same as above except there are more LRAs than MIWaveTileA.
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[3, 6]],
"LRA0": [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 0, 0, 0, 0]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[5, 6, 6, 6, 6, 6, 6, 6]],
"PackB1": [[6, 6, 6, 6, 6, 6, 6, 6]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
# PackA1 at index 5 is invalid because LRs are only guaranteed at index 6 (after SWaitCnt)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA1 @ idx=5 issued too early, must be issued after idx=6 (because of LRA1 issued @ idx=4).")
def test_fail_too_early_less_lrs_plr_pack(self):
"""
Same as above except there are fewer LRAs than MIWaveTileA.
"""
assert self.num_vmfma == 8
optSchedule = {
"SYNC": [[3, 6]],
"LRA0": [[0, 0, 0, 0]],
"LRB0": [[0, 0, 0, 0, 1, 1, 1, 1]],
"PackA0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"PackB0": [[3, 3, 3, 3, 3, 3, 3, 3]],
"LRA1": [[4, 4, 4, 4]],
"LRB1": [[4, 4, 4, 4, 4, 4, 4, 4]],
"PackA1": [[5, 6, 6, 6, 6, 6, 6, 6]],
"PackB1": [[6, 6, 6, 6, 6, 6, 6, 6]],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR1s"),
]
# PackA1 at index 5 is invalid because LRs are only guaranteed at index 6 (after SWaitCnt)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA1 @ idx=5 issued too early, must be issued after idx=6 (because of LRA1 issued @ idx=4).")
# TODO: Tests currently do not validate that the LRs start in the correct quarters.
# Will be added in a future PR since they require modification to the LRs.
class TestValidatePackTF32(CMSValidationTestBase):
"""
Only tests with UsePLRPack since performance is better.
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update({"UsePLRPack": 1, "UseF32XEmulation": True, "ForceUnrollSubIter": True, "UseDirect32XEmulation": True, "DepthU": 32})
super().setup_method(method, kernel_updates=kernel_updates)
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1
self.q2s = self.q1e + 1
self.q2e = self.num_vmfma // 2 - 1
self.q3s = self.q2e + 1
self.q3e = self.num_vmfma // 4 * 3 - 1
self.q4s = self.q3e + 1
self.q4e = self.num_vmfma - 1
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing(self):
"""
Simple passing case.
"""
# 2 A tiles, 2 B tiles, 3 bf16 MFMAs per tile
assert self.num_vmfma == 12
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1, self.q3s+1, self.q4s+1]],
# Must finish before 2nd quarter (i < 3)
"LRA0": [[self.q1s] * 2],
"PackA0": [[self.q1e] * 24],
# Must finish before 3rd quarter (i < 6)
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24],
# Must start after 2nd quarter (i > 5)
"LRB3": [[self.q3s] * 2],
"PackB3": [[self.q3e] * 24],
# Must start after 3rd quarter (i > 8)
"LRA3": [[self.q4s] * 2],
"PackA3": [[self.q4e] * 24],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB3s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA3s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_failing_too_early(self):
"""
Failing case where the PackB0 are issued before the LRB0s are complete.
"""
# 2 A tiles, 2 B tiles, 3 bf16 MFMAs per tile
assert self.num_vmfma == 12
optSchedule = {
"SYNC": [[self.q1s+1, self.q2e]],
# Must finish before 2nd quarter (i < 3)
"LRA0": [[self.q1s] * 2],
"PackA0": [[self.q1e] * 24],
# Must finish before 3rd quarter (i < 6)
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2s] * 24], # There are issued before the SWaitCnt for the LRB0s
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackB0 @ idx=3 issued too early, must be issued after idx=5 (because of LRB0 issued @ idx=3).")
def test_failing_too_late(self):
"""
Failing case where PackA0s are issued to late (after their result is needed by the v_mfmas).
"""
# 2 A tiles, 2 B tiles, 3 bf16 MFMAs per tile
assert self.num_vmfma == 12
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
# Must finish before 2nd quarter (i < 3)
"LRA0": [[self.q1s] * 2],
"PackA0": [[self.q2e] * 24], # Issued after the first mfma in the 2nd quarter
# Must finish before 3rd quarter (i < 6)
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=5 issued too late, must be issued before MFMA @ idx=3.")
def test_failing_too_late_B(self):
"""
Failing case where PackB0s are issued too late (after their result is needed by the v_mfmas).
"""
# 2 A tiles, 2 B tiles, 3 bf16 MFMAs per tile
assert self.num_vmfma == 12
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
# Must finish before 2nd quarter (i < 3)
"LRA0": [[self.q1s] * 2],
"PackA0": [[self.q1e] * 24],
# Must finish before 3rd quarter (i < 6)
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q3e] * 24], # Issued in 3rd quarter, too late
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackB0 @ idx=8 issued too late, must be issued before MFMA @ idx=6.")
def test_failing_not_enough_time_CVT1_MFMA(self):
"""
Failing case where there is not enough time between the last cvt pack command the first "real" mfma using the result.
"""
assert self.num_vmfma == 12
optSchedule = {
"SYNC": [[self.q1s+2, self.q2s+2]],
"SNOP": [[self.q2s]], #
# Must finish before 2nd quarter (i < 3)
"LRA0": [[self.q1s+1] * 2],
"PackA0": [
[self.q1e] * 22 +
[self.q2s] *2
],
# Must finish before 3rd quarter (i < 6)
"LRB0": [[self.q2s+1] * 2],
"PackB0": [[self.q2e] * 24],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
snopCode = [
SNop(waitState=1, comment="Needed to force the last 2 PackA0s to be too close to the MFMA."),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=3 has too little gap between it and MFMA @ idx=4. Expected at least 2 quad-cycles but only 1 passed.", snopCode=snopCode)
class TestValidatePackTF32MFMAReorder(CMSValidationTestBase):
"""
Only tests with UsePLRPack since performance is better.
Use MFMA reorder to swap Q2 and Q3.
With MIWaveTileA=4, MIWaveTileB=4, TF32 (3 MFMAs per tile), we have 48 MFMAs:
- Q1 (indices 0-11): first quarter
- Q2 (indices 12-23): second quarter
- Q3 (indices 24-35): third quarter
- Q4 (indices 36-47): fourth quarter
MFMA reorder swaps Q2 and Q3 (indices 12-23 execute at 24-35 positions and vice versa).
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update(
{
"UsePLRPack": 1, "UseF32XEmulation": True, "UseDirect32XEmulation": True, "ForceUnrollSubIter": True, "DepthU": 32,
"MIWaveTileA": 4, "MIWaveTileB": 4 # Need >= 4 for n_tiles_quarter >= 1
}
)
super().setup_method(method, kernel_updates=kernel_updates)
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1
self.q2s = self.q1e + 1
self.q2e = self.num_vmfma // 2 - 1
self.q3s = self.q2e + 1
self.q3e = self.num_vmfma // 4 * 3 - 1
self.q4s = self.q3e + 1
self.q4e = self.num_vmfma - 1
# MFMA reorder: swap Q2 (indices 12-23) and Q3 (indices 24-35)
self.mfma_reorder = list(range(12)) + list(range(24, 36)) + list(range(12, 24)) + list(range(36, 48))
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing(self):
"""
Simple passing case.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1, self.q3s+1, self.q4s+1]],
"LRA0": [[self.q2s] * 4],
"PackA0": [[self.q2e] * 48],
"LRB0": [[self.q1s] * 4],
"PackB0": [[self.q1e] * 48],
"LRB3": [[self.q3s] * 4],
"PackB3": [[self.q3e] * 48],
"LRA3": [[self.q4s] * 4],
"PackA3": [[self.q4e] * 48],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB3s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA3s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None, mfmaReorder=self.mfma_reorder)
def test_failing_too_early(self):
"""
Failing case where PackB0 are issued too early (before LRB0s are complete).
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1e]],
"LRA0": [[self.q1s] * 4],
"LRB0": [[self.q1s] * 4],
"PackB0": [[self.q1s+1] * 48],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackB0 @ idx=1 issued too early, must be issued after idx=11 (because of LRB0 issued @ idx=0).", mfmaReorder=self.mfma_reorder)
class TestValidatePackTF32CrossPackInterleaving(CMSValidationTestBase):
"""
Tests for TF32 validation with PackA0 and PackB0 interleaving.
Seperate class since more MFMAs are needed to allow for enough room to interleave.
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update({"UsePLRPack": 1, "UseF32XEmulation": True, "UseDirect32XEmulation": True, "ForceUnrollSubIter": True, "DepthU": 32, "MIWaveTileA": 4, "MIWaveTileB": 4})
super().setup_method(method, kernel_updates=kernel_updates)
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing_interleaved(self):
"""
Passing case where PackA0 and PackB0 middle-16 packs are interleaved correctly.
"""
assert self.num_vmfma == 48
packA0_schedule = \
[0] * 4\
+ [ 0,0,
2,2,
4,4,
6,6,
8,8,
10,10,
10,10,
10,10] \
+ [11] * 4 \
+ [11] * 24 # Other set of 24, no conflicts in here.
packB0_schedule = \
[0] * 4 \
+ [ 1,1,
3,3,
5,5,
7,7,
9,9,
11,11,
11,11,
11,11] \
+ [11] * 4 \
+ [11] * 24 # Other set of 24, no conflicts in here.
optSchedule = {
"LRA0": [[-1] * 4],
"LRB0": [[-1] * 4],
"SYNC": [[-1]],
"PackA0": [packA0_schedule],
"PackB0": [packB0_schedule],
}
syncCode = [SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s")]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_failing_interleaved(self):
"""
Failing case where PackA0 and PackB0 middle-16 packs are interleaved incorrectly.
The case fails because the 1st pair (1, 3) in PackB0's middle-16 has a pair of PackA0s middle-16s (2,2) between the producer Pack (1) and consumer Pack (3).
"""
assert self.num_vmfma == 48
packA0_schedule = \
[0] * 4\
+ [ 0,0,
2,2,
4,4,
6,6,
8,8,
10,10,
10,10,
10,10] \
+ [11] * 4 \
+ [11] * 24 # Other set of 24, no conflicts in here.
packB0_schedule = \
[0] * 4 \
+ [ 1,3,
3,3,
5,5,
7,7,
9,9,
11,11,
11,11,
11,11] \
+ [11] * 4 \
+ [11] * 24 # Other set of 24, no conflicts in here.
optSchedule = {
"LRA0": [[-1] * 4],
"LRB0": [[-1] * 4],
"SYNC": [[-1]],
"PackA0": [packA0_schedule],
"PackB0": [packB0_schedule],
}
syncCode = [SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LR0s")]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackB0 @ idx=1 has wrong interleaving. Should have been followed by PackB0 @ idx=3 but was followed by PackA0 @ idx=2.")
class TestValidatePackTF32MultipleGroups(CMSValidationTestBase):
"""
Tests for TF32 pair constraint validation with multiple groups.
The middle-16 packs (indices 4-19 within each group of 24) share a temporary register
and must be scheduled as pairs without any other middle-16 pack between them.
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update(
{
"UsePLRPack": 1, "UseF32XEmulation": True, "UseDirect32XEmulation": True, "ForceUnrollSubIter": True, "DepthU": 32,
"MIWaveTileA": 4, "MIWaveTileB": 2 # 4 A tiles to get 2 groups of 24 packs (48 total)
}
)
super().setup_method(method, kernel_updates=kernel_updates)
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1
self.q2s = self.q1e + 1
self.q2e = self.num_vmfma // 2 - 1
self.q3s = self.q2e + 1
self.q3e = self.num_vmfma // 4 * 3 - 1
self.q4s = self.q3e + 1
self.q4e = self.num_vmfma - 1
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing_two_groups_consecutive(self):
"""
Valid case: 2 groups of 24 packs each, the groups are not interleaved and the middle 16 pairs are consecutive.
"""
# 4 A tiles, 2 B tiles, 3 bf16 MFMAs per tile = 24 vmfmas
assert self.num_vmfma == 24
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 4],
"PackA0": [[self.q1e] * 48],
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_two_groups_interleaved(self):
"""
Valid case: 2 groups of 24 packs each, with the instructions interleaved.
The packs are split into 3 parts: first 4 packs, middle 16, last 4.
The three parts are interleaved between groups, but each part is scheduled as a consecutive block.
"""
# 4 A tiles, 2 B tiles, 3 bf16 MFMAs per tile = 24 vmfmas
assert self.num_vmfma == 24
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 4],
# Do the first 4 packs from each group, then the middle 16 packs, then the last 4 packs.
"PackA0": [
[self.q1s+2] * 4 + [self.q1s+3] * 16 + [self.q1s+4] * 4 +
[self.q1s+2] * 4 + [self.q1s+3] * 16 + [self.q1s+4] * 4
],
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24]
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_two_groups_fully_interleaved(self):
"""
Passing case: 2 groups of 24 packs each, with the instructions interleaved.
The packs are split into 3 parts: first 4 packs, middle 16, last 4.
The the parts are fully interleaved between groups.
"""
# 4 A tiles, 2 B tiles, 3 bf16 MFMAs per tile = 24 vmfmas
assert self.num_vmfma == 24
optSchedule = {
"LRA0": [[-1, -1, -1, -1]],
"SYNC": [[-1, self.q2s+1]],
"PackA0": [
[1,1,3,3] + [3,3, 3,3, 3,3, 3,3, 5,5, 5,5, 5,5, 5,5] + [5,5,5,5] +
[0,0,2,2] + [2,2, 2,2, 2,2, 2,2, 4,4, 4,4, 4,4, 4,4] + [5,5,5,5]
],
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24]
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_failing_two_groups_fully_interleaved(self):
"""
Failing case: 2 groups of 24 packs each, with the instructions interleaved.
The packs are split into 3 parts: first 4 packs, middle 16, last 4.
The the parts are fully interleaved between groups.
The case fails because the second instruction in the middle 16 of the second group is out of order.
"""
# 4 A tiles, 2 B tiles, 3 bf16 MFMAs per tile = 24 vmfmas
assert self.num_vmfma == 24
optSchedule = {
"LRA0": [[-1, -1, -1, -1]],
"SYNC": [[-1, self.q2s+1]],
"PackA0": [
[1,1,3,3] + [3,3, 3,3, 3,3, 3,3, 5,5, 5,5, 5,5, 5,5] + [5,5,5,5] +
[0,0,2,2] + [2,3, 2,2, 2,2, 2,2, 4,4, 4,4, 4,4, 4,4] + [5,5,5,5] # Second instruction in the middle 16 is out of order.
],
"LRB0": [[self.q2s] * 2],
"PackB0": [[self.q2e] * 24]
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, 'PackA0 @ idx=2 has wrong interleaving. Should have been followed by PackA0 @ idx=3 but was followed by PackA0 @ idx=2.')
class TestValidatePackTF32MFMA4x4x4(CMSValidationTestBase):
"""
Tests for TF32 validation with 4x4x4 MFMA.
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update(
{
"UsePLRPack": 1, "UseF32XEmulation": True, "UseMFMAF32XEmulation": True, "UseDirect32XEmulation": True, "ForceUnrollSubIter": True, "DepthU": 32,
"MIWaveTileA": 4, "MIWaveTileB": 4 # 4 A tiles, 4 B tiles, 3 MFMAs per tile = 48 vmfmas
}
)
super().setup_method(method, kernel_updates=kernel_updates)
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1
self.q2s = self.q1e + 1
self.q2e = self.num_vmfma // 2 - 1
self.q3s = self.q2e + 1
self.q3e = self.num_vmfma // 4 * 3 - 1
self.q4s = self.q3e + 1
self.q4e = self.num_vmfma - 1
validator_passes = [add_local_read_constraints, add_pack_constraints]
def test_passing(self):
"""
Simple passing case.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1, self.q3s+1, self.q4s+1]],
"LRA0": [[self.q1s] * 2],
"PackA0": [
[self.q1s+2] * 4 +
[self.q1s+2] * 2 +
[self.q1s+4] * 4 # Needs +2 MFMAs to satisfy the 5 quad-cycle gap.
],
"LRB0": [[self.q2s] * 2],
"PackB0": [
[self.q2s+2] * 4 +
[self.q2s+2] * 2 +
[self.q2s+4] * 4 # Needs +2 MFMAs to satisfy the 5 quad-cycle gap.
],
"LRB3": [[self.q3s] * 2],
"PackB3": [
[self.q3s+2] * 4 +
[self.q3s+2] * 2 +
[self.q3s+4] * 4 # Needs +2 MFMAs to satisfy the 5 quad-cycle gap.
],
"LRA3": [[self.q4s] * 2],
"PackA3": [
[self.q4s+2] * 4 +
[self.q4s+2] * 2 +
[self.q4s+4] * 4 # Needs +2 MFMAs to satisfy the 5 quad-cycle gap.
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB3s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA3s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_failing_too_early(self):
"""
Failing case where the PackB0 are issued before the LRB0s are complete.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2e]],
"LRA0": [[self.q1s] * 2],
"PackA0": [
[self.q1s+2] * 4 +
[self.q1s+2] * 2 +
[self.q1s+4] * 4
],
"LRB0": [[self.q2s] * 2],
"PackB0": [
[self.q2s+1] + # Issued too early.
[self.q3s] * 3 +
[self.q3s] * 2 +
[self.q3s+2] * 4
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackB0 @ idx=13 issued too early, must be issued after idx=23 (because of LRB0 issued @ idx=12).")
def test_failing_too_late(self):
"""
Failing case where PackA0s are issued too late (after their result is needed by the v_mfmas).
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
# Must finish before 2nd quarter
"LRA0": [[self.q1s] * 2],
"PackA0": [
[self.q1s+1] * 4 +
[self.q1s+1] * 2 +
[self.q2s+2] * 4 # Issued after the 2nd mfma in the 2nd quarter
],
# Must finish before 3rd quarter
"LRB0": [[self.q2s] * 2],
"PackB0": [
[self.q2s+2] * 4 +
[self.q2s+2] * 2 +
[self.q2s+3] * 4
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=14 issued too late, must be issued before MFMA @ idx=13.")
def test_passing_snop(self):
"""
Passing case that relies on s_nop instructions to guarantee there is enough time around the 4x4 MFMAs.
This test verifies that SNop instructions are properly counted for quad-cycle spacing.
The first 4 packs (CVT0) need 2 quad-cycles before their result is used by the 4x4 MFMA.
For TF32 4x4 MFMA with MIWaveTileA=4, MIWaveTileB=4:
- 48 total vmfmas (4*4*3)
- q1: 0-11, q2: 12-23, q3: 24-35, q4: 36-47
- PackA0 packs are needed for MFMAs in q2 starting at idx 12
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"SNOP": [[self.q1s+2, self.q2s+2]],
# Must finish before 2nd quarter
"LRA0": [[self.q1s, self.q1s]],
"PackA0": [
[self.q1s+1] * 4 +
[self.q1s+1] +
[self.q1s+1] + # Only 2 quad-cycles before Pack[6] without SNop.
[self.q1s+2] * 4
],
# Must finish before 3rd quarter
"LRB0": [[self.q2s, self.q2s]],
"PackB0": [
[self.q2s+1] * 4 +
[self.q2s+1] +
[self.q2s+1] + # Only 2 quad-cycles before Pack[6] without SNop.
[self.q2s+2] * 4
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
snopCode = [
SNop(waitState=1, comment="Needed to have 5 quad-cycle space between PackA0[5] and PackBo[6]."),
SNop(waitState=1, comment="Needed to have 5 quad-cycle space between PackB0[5] and PackB0[6]."),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None, snopCode=snopCode)
def test_failing_not_enough_time_CVTO_MFMA(self):
"""
Failing case where there is not enough time between the first pair of CVT0 and the first 4x4 MFMA.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 2],
"PackA0": [
[self.q1s+1] * 2 +
[self.q1s+2] * 2 +
[self.q1s+1] +
[self.q1s+3] +
[self.q1s+4] * 4
],
"LRB0": [[self.q2s] * 2],
"PackB0": [
[self.q2s+1] * 2 +
[self.q2s+2] * 2 +
[self.q2s+1] +
[self.q2s+3] +
[self.q2s+4] * 4
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=1 has too little gap between it and PackA0 @ idx=1. Expected at least 2 quad-cycles but only 1 passed.")
def test_failing_not_enough_time_MFMA_CVT1(self):
"""
Failing case where there is not enough time between the 4x4 MFMA (Pack 5) and the first CVT1 pack (Pack 6).
Pack 5 (second 4x4 MFMA) needs 5 quad-cycles before Pack 6 (first CVT1) can use its result.
With only 1 standard MFMA between them, there's only 3 quad-cycles, which is not enough.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 2],
"PackA0": [
[self.q1s+2] * 4 +
[self.q1s+2] * 2 +
[self.q1s+3] * 4 # CVT1: indices 6-9 at vmfma 3 (only 1 MFMA between, not enough time!)
],
"LRB0": [[self.q2s] * 2],
"PackB0": [
[self.q2s+2] * 4 +
[self.q2s+2] * 2 +
[self.q2s+4] * 4 # CVT1 at vmfma q2s+4, enough time for B
],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=2 has too little gap between it and PackA0 @ idx=3. Expected at least 5 quad-cycles but only 3 passed.")
def test_failing_not_enough_time_CVT1_MFMA(self):
"""
Failing case where there is not enough time between the last CVT1 pack and the first "real" MFMA using the result.
CVT1 packs (indices 6-9) need 2 quad-cycles before the MFMAs can use their results.
"""
assert self.num_vmfma == 48
optSchedule = {
"SYNC": [[self.q1s+2, self.q2s+2]],
"SNOP": [[self.q2s]],
"LRA0": [[self.q1s+1] * 2],
"PackA0": [
[self.q1s+2] * 4 +
[self.q1s+2] * 2 +
[self.q1e] * 2 +
[self.q2s] * 2 # CVT1 (8-9) at vmfma 12 (too close to MFMA at q2s+1)
],
"LRB0": [[self.q2s+1] * 2],
"PackB0": [[self.q2e] * 10],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s")
]
snopCode = [
SNop(waitState=1, comment="Needed to force the last 2 PackA0s to be too close to the MFMA."),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=12 has too little gap between it and MFMA @ idx=13. Expected at least 2 quad-cycles but only 1 passed.", snopCode=snopCode)
class TestValidatePackTF32MFMA4x4x4MultipleTiles(CMSValidationTestBase):
"""
Tests for TF32 4x4 MFMA validation with multiple tiles (multiple groups of 10 packs).
This tests that the tile-based offset is correctly calculated for each group.
With MIWaveTileA=4, there are 4 A tiles, each requiring 10 pack instructions (40 total PackA0).
Each tile's packs should be needed by different MFMAs:
- Tile 0 (packs 0-9): needed by MFMAs at base_offset + 0
- Tile 1 (packs 10-19): needed by MFMAs at base_offset + 3
- Tile 2 (packs 20-29): needed by MFMAs at base_offset + 6
- Tile 3 (packs 30-39): needed by MFMAs at base_offset + 9
"""
def setup_method(self, method=None, *, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates.update(
{
"UsePLRPack": 1, "UseF32XEmulation": True, "UseMFMAF32XEmulation": True, "UseDirect32XEmulation": True, "ForceUnrollSubIter": True, "DepthU": 32,
"MIWaveTileA": 4, "MIWaveTileB": 2 # 4 A tiles, 2 B tiles, 3 MFMAs per tile = 24 vmfmas
}
)
super().setup_method(method, kernel_updates=kernel_updates)
# With 4 A tiles, 2 B tiles, 3 MFMAs per tile = 24 vmfmas
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1 # 5
self.q2s = self.q1e + 1 # 6
self.q2e = self.num_vmfma // 2 - 1 # 11
self.q3s = self.q2e + 1 # 12
self.q3e = self.num_vmfma // 4 * 3 - 1 # 17
self.q4s = self.q3e + 1 # 18
self.q4e = self.num_vmfma - 1 # 23
validator_passes = [add_local_read_constraints, add_pack_constraints]
def _make_valid_pack_group(self, base_idx: int) -> list[int]:
"""
Creates a valid group of 10 packs with proper spacing for 4x4 MFMA TF32.
Pack 5 (second 4x4 MFMA) needs 5 quad-cycles before Pack 6 (first CVT1).
"""
return (
[base_idx] * 4 + # CVT0 (packs 0-3)
[base_idx] * 2 + # 4x4 MFMAs (packs 4-5)
[base_idx + 2] * 4 # CVT1 (packs 6-9) - needs +2 MFMA indices for 5 quad-cycle gap
)
def test_passing_multiple_tiles_different_timings(self):
"""
Passing case where 4 groups of 10 packs are scheduled at different times
appropriate for their respective tiles.
- Tile 0 packs (0-9): needed by MFMAs 6-8 (q2s, q2s+1, q2s+2)
- Tile 1 packs (10-19): needed by MFMAs 9-11 (q2s+3, q2s+4, q2s+5)
- Tile 2 packs (20-29): needed by MFMAs 6-8 (for B tile 1)
- Tile 3 packs (30-39): needed by MFMAs 9-11 (for B tile 1)
All packs are scheduled early in Q1 with proper spacing.
"""
assert self.num_vmfma == 24
# Each tile's packs have proper 5 quad-cycle gap for 4x4 MFMA
packA0_schedule = (
self._make_valid_pack_group(self.q1s+2) + # Tile 0: base at 2, CVT1 at 4
self._make_valid_pack_group(self.q1s+2) + # Tile 1
self._make_valid_pack_group(self.q1s+2) + # Tile 2
self._make_valid_pack_group(self.q1s+2) # Tile 3
)
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 4], # 4 LRs for 4 tiles
"PackA0": [packA0_schedule],
"LRB0": [[self.q2s] * 2],
"PackB0": [self._make_valid_pack_group(self.q2s+2) + self._make_valid_pack_group(self.q2s+2)],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_tile1_packs_after_tile0_deadline(self):
"""
Passing case that exercises the tile-based offset calculation.
Tile 0 packs (0-9) are needed by MFMAs starting at q2s (index 6).
Tile 1 packs (10-19) are needed by MFMAs starting at q2s+3 (index 9).
We schedule tile 1's CVT0 packs at index 6, which would be INVALID if they
were incorrectly assigned to tile 0's needed_by (index 6) since 6 >= 6, but
is VALID because they should be assigned to tile 1's needed_by (index 9).
This test specifically validates that the tile offset is correctly applied:
- Without the fix, pack 10-13 (tile 1's CVT0) at index 6 would be checked
against tile 0's needed_by (MFMA 6), failing with "too late" since 6 >= 6
- With the fix, pack 10-13 is correctly checked against tile 1's needed_by
(MFMA 9), which passes since 6 < 9
CVT1 needed_by for tile 1 is MFMA at index 10 (base=6 + tile_offset=3 + pack_offset=1).
We place CVT1 at index 8 (base 6 + 2) which is before MFMA 10.
"""
assert self.num_vmfma == 24
# Tile 0: schedule at index 2 (valid, well before needed_by at 6)
# Tile 1: base at 6 (CVT0/4x4 at 6, CVT1 at 8)
# - CVT0 at 6 would fail if checked against tile 0's needed_by (MFMA 6)
# - CVT0 at 6 passes when correctly checked against tile 1's needed_by (MFMA 9)
# - CVT1 at 8 is before tile 1's CVT1 needed_by (MFMA 10)
packA0_schedule = (
self._make_valid_pack_group(self.q1s+2) + # Tile 0: base at 2, CVT1 at 4, before needed_by at 6
self._make_valid_pack_group(self.q2s) + # Tile 1: base at 6, CVT1 at 8, before needed_by at 9/10
self._make_valid_pack_group(self.q1s+2) + # Tile 2
self._make_valid_pack_group(self.q1s+2) # Tile 3
)
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 4],
"PackA0": [packA0_schedule],
"LRB0": [[self.q2s] * 2],
"PackB0": [self._make_valid_pack_group(self.q2s+2) + self._make_valid_pack_group(self.q2s+2)],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_failing_tile1_packs_actually_too_late(self):
"""
Failing case where tile 1's packs are scheduled too late for tile 1's actual deadline.
Tile 1 packs (10-19) are needed by MFMAs starting at q2s+3 (index 9).
We schedule tile 1's CVT0 packs at index 9, which is AT the needed_by index,
meaning they're too late (must be strictly before).
"""
assert self.num_vmfma == 24
# Tile 1: CVT0 at index 9 - exactly at the needed_by, which is too late
packA0_schedule = (
self._make_valid_pack_group(self.q1s+2) + # Tile 0: valid
[self.q2s+3] * 4 + [self.q2s+3] * 2 + [self.q2s+5] * 4 + # Tile 1: CVT0 at 9 (too late!)
self._make_valid_pack_group(self.q1s+2) + # Tile 2
self._make_valid_pack_group(self.q1s+2) # Tile 3
)
optSchedule = {
"SYNC": [[self.q1s+1, self.q2s+1]],
"LRA0": [[self.q1s] * 4],
"PackA0": [packA0_schedule],
"LRB0": [[self.q2s] * 2],
"PackB0": [self._make_valid_pack_group(self.q2s+2) + self._make_valid_pack_group(self.q2s+2)],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRB0s"),
]
# Tile 1's CVT0 at index 9 is too late - needed by MFMA at index 9
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=9 issued too late, must be issued before MFMA @ idx=9.")
def test_broken_pack_schedule_with_mfma_reorder(self):
"""
Test that the validator detects pack scheduling bugs (read-before-write hazards)
with complex MFMA reordering.
Broken PackA0 schedule (packs at idx 27-30, MFMA needs them at idx 26) should fail.
This is a real-world 128x256x32 TF32 NT configuration with MFMA reordering.
"""
# Minimal kernel config for 128x256x32 TF32 NT validation
kernel = {
"UseF32XEmulation": True, "UseDirect32XEmulation": True, "UseMFMAF32XEmulation": True,
"ForceUnrollSubIter": True, "DirectToLds": True, "SwapGlobalReadOrder": 0,
"UsePLRPack": 1, "MIWaveTileA": 4, "MIWaveTileB": 8, "Use64bShadowLimit": 1,
"VectorWidthA": 1, "VectorWidthB": 1,
"ProblemType": {"TLUA": True, "TLUB": True},
}
# Hardcoded schedule from _get_schedule_128x256x32_TF32 NT case
# with broken PackA0: packs at indices 27-30, but MFMA at index 26 needs their results
schedule_info = ScheduleInfo(
numCodePaths=2,
numMfma=96,
optSchedule={
# BROKEN PackA0: indices 27-30 instead of 24-26, causing read-before-write
"PackA0": [[
20, 20, 21, 21,
23, 23,
28, 28, 28, 28,
27, 27, 27, 27,
28, 28,
30, 30, 30, 30]],
"SYNC": [[1, 1, 3, 49, 78]],
"GRIncA": [[0]*9],
"GRIncB": [[0]*9],
"LRA0": [[0]*16],
"LRB0": [[0]*32],
"LRA3": [[77]*16],
"LRB3": [[48]*32],
"LCC": [[0]*9],
"LWSA": [[0]],
"LWSB": [[0]],
"LWRA": [[0]],
"LWRA": [[0]],
"GRA": [[2]*8],
"GRB": [[2]*8],
"PackB0": [[42, 42, 42, 43, 48, 48, 49, 49, 49, 50, 43, 43, 44, 44, 48, 48, 50, 50, 50, 51, 45, 45, 46, 46, 48, 48, 51, 51, 52, 52, 46, 47, 47, 47, 48, 48, 52, 53, 53, 53]],
"PackB3": [[67, 67, 68, 68, 75, 75, 76, 76, 77, 77, 69, 69, 70, 70, 75, 75, 78, 78, 86, 86, 71, 71, 72, 72, 75, 75, 87, 87, 88, 88, 73, 73, 74, 74, 75, 75, 89, 89, 90, 90]],
"PackA3": [[89, 89, 90, 90, 92, 92, 93, 93, 93, 94, 90, 91, 91, 91, 92, 92, 94, 94, 95, 95]],
},
syncCode=[
SWaitCnt(dscnt=0),
SBarrier(comment=""),
SWaitCnt(vlcnt=0),
SWaitCnt(dscnt=0),
SWaitCnt(dscnt=0),
],
nglshift=12,
nllshift=12,
mfmaReorder=[
0, 1, 2, 3,
4, 5, 6, 7,
8, 9, 10, 11,
12, 13, 14, 15,
16, 17, 18, 19,
20, 21, 22, 23,
# Reordered to do AhBh, AhBl, AlBh in row-major order.
# Gives more time to finish Al's
24, 26, 30, 32,
36, 38, 42, 44,
25, 31, 37, 43,
27, 29, 33, 35,
39, 41, 45, 47,
28, 34, 40, 46,
# Reordered to do AhBh, AlBh, AhBl in column-major order.
# Gives more time to finish Bl's
48, 49, 51, 52,
50, 53, 54, 55,
57, 58, 56, 59,
60, 61, 63, 64,
62, 65, 66, 67,
69, 70, 68, 71,
72, 73, 74, 75,
76, 77, 78, 79,
80, 81, 82, 83,
84, 85, 86, 87,
88, 89, 90, 91,
92, 93, 94, 95],
)
valid, message = isValid(schedule_info, {"kernel": kernel})
assert valid # Schedule previously failed, now passes.
# Check it fails where it's expected to.
schedule_info.optSchedule["PackA0"] = [[
20, 20, 21, 21,
23, 23,
28, 28, 28, 28,
37, 37, 37, 37,
40, 40,
41, 41, 41, 41]]
valid, message = isValid(schedule_info, {"kernel": kernel})
assert not valid
assert message == "Code path 0: PackA0 @ idx=37 issued too late, must be issued before MFMA @ idx=36."
class TestValidatePackTF32MFMA4x4x4SwapPacks(CMSValidationTestBase):
"""
Tests for TF32 4x4 MFMA validation with VectorWidth > 1 (swap packs).
When VW > 1, VSwapB32 instructions appear at the beginning of the pack sequence
to transpose registers after wider local reads. Count: 4 * (vw - 1) per side.
With ForceUnrollSubIter, both LRA0 and LRB0 are needed by MFMAs starting at
index 24 (q2s), so both must be issued and guaranteed before index 24. We place
both LR groups and their SWaitCnt early in q1.
Parametrized over (DepthU, SUB_ITER_SUFFIX, ForceUnrollSubIter):
- (64, "1", False): DepthU > matrixInstK, standard sub-iteration naming
- (32, "3", True): DepthU == matrixInstK, ForceUnrollSubIter, A/B3 suffix
- (32, "1", False): DepthU == matrixInstK, no ForceUnrollSubIter, A/B1 suffix
"""
@pytest.fixture(autouse=True, params=[
pytest.param((64, "1", False), id="DU64"),
pytest.param((32, "3", True), id="DU32_ForceUnroll"),
pytest.param((32, "1", False), id="DU32_NoForceUnroll"),
])
def _config(self, request):
self.DEPTH_U, self.SUB_ITER_SUFFIX, self.FORCE_UNROLL_SUB_ITER = request.param
def setUp(self, kernel_updates: Optional[dict[str, Any]] = None) -> None:
kernel_updates = kernel_updates.copy() if kernel_updates else {}
kernel_updates["UsePLRPack"] = 1
kernel_updates["UseF32XEmulation"] = True
kernel_updates["UseMFMAF32XEmulation"] = True
kernel_updates["UseDirect32XEmulation"] = True
kernel_updates["ForceUnrollSubIter"] = self.FORCE_UNROLL_SUB_ITER
kernel_updates["DepthU"] = self.DEPTH_U
kernel_updates.setdefault("MIWaveTileA", 4)
kernel_updates.setdefault("MIWaveTileB", 4)
kernel_updates.setdefault("VectorWidthA", 1)
kernel_updates.setdefault("VectorWidthB", 1)
super().setup_method(kernel_updates=kernel_updates)
self.q1s = 0
self.q1e = self.num_vmfma // 4 - 1
self.q2s = self.q1e + 1
self.q2e = self.num_vmfma // 2 - 1
self.q3s = self.q2e + 1
self.q3e = self.num_vmfma // 4 * 3 - 1
self.q4s = self.q3e + 1
self.q4e = self.num_vmfma - 1
validator_passes = [add_local_read_constraints, add_pack_constraints]
def _make_valid_pack_group(self, base_idx: int) -> list[int]:
"""Creates a valid group of 10 packs with proper spacing for 4x4 MFMA TF32."""
return (
[base_idx] * 4 + # CVT0 (packs 0-3)
[base_idx] * 2 + # 4x4 MFMAs (packs 4-5)
[base_idx + 2] * 4 # CVT1 (packs 6-9) - needs +2 MFMA indices for 5 quad-cycle gap
)
def _make_base_schedule(self, packA0_schedule, packB0_schedule,
pack_alt_a=None, pack_alt_b=None,
n_lrs_a=2, n_lrs_b=2):
"""
Build a schedule with LRs early in q1 (both guaranteed before q2s).
"""
s = self.SUB_ITER_SUFFIX
if pack_alt_a is None:
pack_alt_a = self._make_valid_pack_group(self.q4s+2)
if pack_alt_b is None:
pack_alt_b = self._make_valid_pack_group(self.q3s+2)
optSchedule = {
# One SYNC at idx 1 guarantees both LRA0+LRB0 (needed before q2s).
# Separate SYNCs for LRB{s} and LRA{s}.
"SYNC": [[1, self.q3s+1, self.q4s+1]],
"LRA0": [[0] * n_lrs_a],
"PackA0": [packA0_schedule],
"LRB0": [[0] * n_lrs_b],
"PackB0": [packB0_schedule],
f"LRB{s}": [[self.q3s] * n_lrs_b],
f"PackB{s}": [pack_alt_b],
f"LRA{s}": [[self.q4s] * n_lrs_a],
f"PackA{s}": [pack_alt_a],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0+LRB0"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment=f"Wait for LRB{s}s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment=f"Wait for LRA{s}s"),
]
return optSchedule, syncCode
def _build_swap_schedule(self, vw, base_idx):
"""Build swap packs + regular pack groups for a given VectorWidth."""
n_swaps = 4 * (vw - 1)
return [base_idx] * n_swaps + self._make_valid_pack_group(base_idx) * vw
@pytest.mark.parametrize("vw_a,vw_b,extra_kernel", [
pytest.param(1, 1, {}, id="vw1_both"),
pytest.param(2, 1, {}, id="vw2_a_only"),
pytest.param(4, 1, {}, id="vw4_a_only"),
pytest.param(2, 2, {"ProblemType": {"TLUB": True}}, id="vw2_both"),
])
def test_passing_vw_combinations(self, vw_a, vw_b, extra_kernel):
"""Valid schedules with various VectorWidth combinations."""
kernel = {"VectorWidthA": vw_a, "VectorWidthB": vw_b}
kernel.update(extra_kernel)
self.setUp(kernel)
packA0 = self._build_swap_schedule(vw_a, 2)
packB0 = self._build_swap_schedule(vw_b, 2)
pack_alt_a = self._build_swap_schedule(vw_a, self.q4s + 2)
pack_alt_b = self._build_swap_schedule(vw_b, self.q3s + 2)
n_lrs_a = 8 if vw_a >= 2 else 2
n_lrs_b = 8 if vw_b >= 2 else 2
optSchedule, syncCode = self._make_base_schedule(
packA0, packB0,
pack_alt_a=pack_alt_a, pack_alt_b=pack_alt_b,
n_lrs_a=n_lrs_a, n_lrs_b=n_lrs_b)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_passing_multiple_groups_with_swaps(self):
"""VW_A=2, MIWaveTileA=4, MIWaveTileB=2. 2 groups of 10 regular packs + 4 swap packs on A, 2 groups on B."""
self.setUp({"VectorWidthA": 2, "VectorWidthB": 1, "MIWaveTileA": 4, "MIWaveTileB": 2})
# Both LRA0 and LRB0 at idx 0, guaranteed by SYNC at idx 1
# 4 swap packs + 2 groups of 10 regular packs = 24 total PackA0
# 8 LRs needed: dsReadConvTable has 8 entries for VW=2
packA0_schedule = (
[2] * 4 +
self._make_valid_pack_group(2) +
self._make_valid_pack_group(2)
)
optSchedule = {
"SYNC": [[1]],
"LRA0": [[0] * 8],
"PackA0": [packA0_schedule],
"LRB0": [[0] * 2],
"PackB0": [self._make_valid_pack_group(2) + self._make_valid_pack_group(2)],
}
syncCode = [
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Wait for LRA0+LRB0"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_fail_swap_before_lr_done(self):
"""SwapPacks issued before LR SWaitCnt. Expected: 'issued too early' error."""
self.setUp({"VectorWidthA": 2, "VectorWidthB": 1})
# Swap packs at idx 0, but SYNC is at idx 1, so they're before LR is guaranteed
packA0 = [0] * 4 + self._make_valid_pack_group(2) * 2
packB0 = self._make_valid_pack_group(2)
pack_alt_a = [self.q4s+2] * 4 + self._make_valid_pack_group(self.q4s+2) * 2
optSchedule, syncCode = self._make_base_schedule(packA0, packB0, pack_alt_a=pack_alt_a, n_lrs_a=8)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=0 issued too early, must be issued after idx=1 (because of LRA0 issued @ idx=0).")
def test_fail_regular_pack_before_swap_done(self):
"""First CVT0 pack issued before its swap dependency is done."""
self.setUp({"VectorWidthA": 2, "VectorWidthB": 1})
# Swap packs at idx 2, group 0's CVT0 at idx 1 (before swaps!)
# Group 0's CVT0 pack 0 reads reg 1 which was swapped by swap 0, so it depends on swap 0.
packA0 = (
[2] * 4 + # 4 swap packs at idx 2
[1] * 4 + # CVT0 group 0 at idx 1, BEFORE the swap packs!
[2] * 2 + # 4x4 MFMAs group 0
[4] * 4 + # CVT1 group 0
self._make_valid_pack_group(2) # group 1 valid
)
packB0 = self._make_valid_pack_group(2)
pack_alt_a = [self.q4s+2] * 4 + self._make_valid_pack_group(self.q4s+2) * 2
optSchedule, syncCode = self._make_base_schedule(packA0, packB0, pack_alt_a=pack_alt_a, n_lrs_a=8)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, "PackA0 @ idx=1 issued too early, must be issued after idx=2 (because of PackA0 issued @ idx=2).")
def test_swap_depends_on_specific_lrs_vw4(self):
"""VW=4: T0 swaps (0,1,2) depend only on T0 LRs (LR0-LR3). Placing them
after a partial guarantee (dscnt=4, guaranteeing LR0-LR3) should pass."""
self.setUp({"VectorWidthA": 4, "VectorWidthB": 1})
s = self.SUB_ITER_SUFFIX
# 8 A-side LRs all at idx 0, 2 B-side LRs at idx 0.
# SYNC(dscnt=4) at idx 1: leaves 4 in flight (LRA0[4..7]), guarantees LRA0[0..3] + LRB0[0..1].
# SYNC(dscnt=0) at idx 6: guarantees remaining LRA0[4..7].
#
# Under T/X interleave mapping (_logical_reg_to_lr_index):
# LR0-LR3 are T0 LRs (regs with idx%8 < 4)
# LR4-LR7 are X0 LRs (regs with idx%8 >= 4)
#
# T0 swaps (0,1,2,6,7,10) only depend on LR0-LR3 → can be placed early.
# X0 swaps (3,4,5,8,9,11) depend on LR4-LR7 → must wait for full guarantee.
swap_schedule = (
[2] * 3 + # swaps 0-2 (T0: regs 1↔8, 2↔16, 3↔24) → ok after idx 1
[7] * 3 + # swaps 3-5 (X0: regs 5↔12, 6↔20, 7↔28) → after idx 6
[2] * 2 + # swaps 6-7 (T0: regs 10↔17, 11↔25) → ok after idx 1
[7] * 2 + # swaps 8-9 (X0: regs 14↔21, 15↔29) → after idx 6
[2] + # swap 10 (T0: regs 19↔26) → ok after idx 1
[7] # swap 11 (X0: regs 23↔30) → after idx 6
)
packA0 = list(swap_schedule) + self._make_valid_pack_group(8) * 4
packB0 = self._make_valid_pack_group(2)
pack_alt_a = [self.q4s+2] * 12 + self._make_valid_pack_group(self.q4s+2) * 4
optSchedule = {
"SYNC": [[1, 6, self.q3s+1, self.q4s+1]],
# LRB0 before LRA0 so dscnt=4 skips only LRA0[4..7] (most recent 4),
# guaranteeing LRB0[0..1] + LRA0[0..3].
"LRB0": [[0] * 2],
"PackB0": [packB0],
"LRA0": [[0] * 8],
"PackA0": [packA0],
f"LRB{s}": [[self.q3s] * 2],
f"PackB{s}": [self._make_valid_pack_group(self.q3s+2)],
f"LRA{s}": [[self.q4s] * 8],
f"PackA{s}": [pack_alt_a],
}
syncCode = [
SWaitCnt(dscnt=4, vlcnt=-1, vscnt=-1, comment="Partial: guarantee LRA0[0..3]+LRB0"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment="Full: guarantee remaining LRA0[4..7]"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment=f"Wait for LRB{s}s"),
SWaitCnt(dscnt=0, vlcnt=-1, vscnt=-1, comment=f"Wait for LRA{s}s"),
]
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)
def test_cvt0_depends_on_specific_swaps_vw4(self):
"""VW=4: group 2's CVT0 packs depend on swaps that touch regs 16-23,
not on all swaps. Placing group 2's CVT0 before swaps that only affect
other groups should pass."""
self.setUp({"VectorWidthA": 4, "VectorWidthB": 1})
# With VW=4, 4 groups, the swap register pairs are:
# swap 0: 1↔8 (groups 0,1)
# swap 1: 2↔16 (groups 0,2) ← group 2 depends on this
# swap 2: 3↔24 (groups 0,3)
# swap 3: 5↔12 (groups 0,1)
# swap 4: 6↔20 (groups 0,2) ← group 2 depends on this
# swap 5: 7↔28 (groups 0,3)
# swap 6: 10↔17 (groups 1,2) ← group 2 depends on this
# swap 7: 11↔25 (groups 1,3)
# swap 8: 14↔21 (groups 1,2) ← group 2 depends on this
# swap 9: 15↔29 (groups 1,3)
# swap 10: 19↔26 (groups 2,3) ← group 2 depends on this
# swap 11: 23↔30 (groups 2,3) ← group 2 depends on this
#
# Group 2 (regs 16-23) depends on swaps 1,4,6,8,10,11.
# Group 2 does NOT depend on swaps 0,2,3,5,7,9.
#
# Strategy: place all swaps at idx 2, but put group 2's CVT0 packs at idx 3
# (after all swaps). This should pass for group 2 since its dependencies are satisfied.
# To make the test meaningful, place swaps 0,2,3,5,7,9 at idx 4 (AFTER group 2's CVT0).
# Group 2's CVT0 at idx 3 should still pass because it doesn't depend on those late swaps.
swap_schedule = (
[4] + # swap 0 (groups 0,1) → late
[2] + # swap 1 (groups 0,2) → early
[4] + # swap 2 (groups 0,3) → late
[4] + # swap 3 (groups 0,1) → late
[2] + # swap 4 (groups 0,2) → early
[4] + # swap 5 (groups 0,3) → late
[2] + # swap 6 (groups 1,2) → early
[4] + # swap 7 (groups 1,3) → late
[2] + # swap 8 (groups 1,2) → early
[4] + # swap 9 (groups 1,3) → late
[2] + # swap 10 (groups 2,3) → early
[2] # swap 11 (groups 2,3) → early
)
# Group 0 and 1 CVT0 packs at idx 5 (after ALL swaps including late ones at idx 4)
# Group 2 CVT0 packs at idx 3 (after early swaps at idx 2 but before late swaps at idx 4)
# Group 3 CVT0 packs at idx 5 (after ALL swaps)
packA0 = (
list(swap_schedule) +
[5] * 4 + [5] * 2 + [7] * 4 + # group 0: CVT0@5, MFMA@5, CVT1@7
[5] * 4 + [5] * 2 + [7] * 4 + # group 1: CVT0@5, MFMA@5, CVT1@7
[3] * 4 + [3] * 2 + [5] * 4 + # group 2: CVT0@3 (early!), MFMA@3, CVT1@5
[5] * 4 + [5] * 2 + [7] * 4 # group 3: CVT0@5, MFMA@5, CVT1@7
)
packB0 = self._make_valid_pack_group(2)
pack_alt_a = [self.q4s+2] * 12 + self._make_valid_pack_group(self.q4s+2) * 4
optSchedule, syncCode = self._make_base_schedule(packA0, packB0, pack_alt_a=pack_alt_a, n_lrs_a=8)
self.validate(optSchedule, syncCode, 1, 2, 2, 0, None)