Skip to content

Commit 49f5d27

Browse files
committed
Updated spec with Tanner feedback
1 parent cb76ba1 commit 49f5d27

4 files changed

Lines changed: 131 additions & 26 deletions

File tree

accelforge/frontend/workload.py

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@
33
"""
44

55
import copy
6-
from itertools import product
7-
import itertools
8-
import logging
96
import re
10-
from typing import Annotated, Any, TypeAlias
7+
from typing import Annotated, Any, TypeAlias, Union
118

129
import pydot
10+
from pydantic import Discriminator, Tag
1311

1412
from accelforge.util.parallel import _SVGJupyterRender
1513

@@ -19,6 +17,7 @@
1917
EvalableList,
2018
EvalableModel,
2119
EvalsTo,
20+
_get_tag,
2221
)
2322
from accelforge.util._visualization import _pydot_graph
2423
from accelforge.frontend.renames import (
@@ -833,22 +832,29 @@ class Adapter(EvalableModel):
833832
"""
834833
name: EvalsTo[str]
835834

836-
adapter_class: EvalsTo[str]
837-
"""
838-
The class of the adapter. Supported classes are:
839-
- copy
840-
- collective
841-
"""
842835

836+
class CopyAdapter(Adapter):
843837
tensor: EvalsTo[str]
844838

845839

840+
EinsumOrAdapter = Annotated[
841+
Union[
842+
Annotated["Einsum", Tag("Einsum")],
843+
Annotated["CopyAdapter", Tag("Copy")],
844+
],
845+
Discriminator(lambda v: _get_tag(v, default="Einsum")),
846+
]
847+
848+
846849
class Workload(EvalableModel):
847850
"""
848851
The workload specification as a cascade of Einsums, with each Einsum being a
849852
computation step in the workload.
850853
"""
851854

855+
einsums_and_adapters: EvalableList[EinsumOrAdapter] = EvalableList()
856+
"""Einsums and adapters in the workload."""
857+
852858
einsums: EvalableList[Einsum] = EvalableList()
853859
""" The Einsums in the workload. """
854860

@@ -891,11 +897,6 @@ class Workload(EvalableModel):
891897
matching tensors as persistent. Example: "weight" or "~(Outputs | Intermediates)".
892898
"""
893899

894-
adapters: EvalableList[Adapter] = EvalableList()
895-
"""
896-
Adapters for shared tensors.
897-
"""
898-
899900
def _for_einsum(self, einsum_name: EinsumName) -> "Workload":
900901
"""Return a copy of the workload with only the Einsum with the given name."""
901902
new = self.model_copy(deep=False)
@@ -913,6 +914,8 @@ def __init__(self, **data):
913914
processed_einsums.append(_parse_einsum_entry(einsum_entry))
914915
data["einsums"] = processed_einsums
915916

917+
self._canonicalize_einsums_and_adapters(data)
918+
916919
super().__init__(**data)
917920

918921
def model_post_init(self, __context__=None) -> None:
@@ -953,6 +956,26 @@ def _validate(self):
953956
)}"
954957
)
955958

959+
def _canonicalize_einsums_and_adapters(self, data):
960+
"""
961+
Checks that only one of einsums or einsums_and_adapters is specified
962+
and fills in the other.
963+
"""
964+
has_einsums = "einsums" in data and len(data["einsums"]) > 0
965+
has_einsums_and_adapters = "einsums_and_adapters" in data and len(data["einsums_and_adapters"]) > 0
966+
967+
if has_einsums and has_einsums_and_adapters:
968+
raise EvaluationError("Please use only one of keywords einsums or einsums_and_adapters")
969+
970+
def is_einsum(d):
971+
return not hasattr(d, "tag") or d.tag == "!Einsum"
972+
973+
if has_einsums:
974+
data["einsums_and_adapters"] = data["einsums"]
975+
data["einsums"] = [e for e in data["einsums_and_adapters"] if is_einsum(e)]
976+
elif has_einsums_and_adapters:
977+
data["einsums"] = [e for e in data["einsums_and_adapters"] if is_einsum(e)]
978+
956979
@property
957980
def einsum_names(self) -> list[EinsumName]:
958981
"""Returns the names of the Einsums in the workload."""
@@ -1334,9 +1357,3 @@ def get_compute_intensity(self, einsum_name: str) -> float:
13341357
self.get_tensor_size(tensor)
13351358
for tensor in self.einsums[einsum_name].tensor_names
13361359
)
1337-
1338-
def get_adapter_for(self, tensor_name: TensorName) -> Adapter:
1339-
for adapter in self.adapters:
1340-
if adapter.tensor == tensor_name:
1341-
return adapter
1342-
return ValueError(f"No adapter found for tensor {tensor_name}")

accelforge/util/_basetypes.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import os
55
from pathlib import Path
66
import re
7+
import ruamel.yaml
78
from pydantic import BaseModel, ConfigDict, Tag, ValidationError
89
from pydantic.main import IncEx
910
from pydantic_core.core_schema import (
@@ -59,7 +60,7 @@
5960
Ts = TypeVarTuple("Ts")
6061

6162

62-
def _get_tag(value: Any) -> str:
63+
def _get_tag(value: Any, default: str=None) -> str:
6364
if not isinstance(value, dict):
6465
return value.__class__.__name__
6566
tag = None
@@ -81,14 +82,22 @@ def try_index(attr: str) -> str:
8182
break
8283
if tag := try_index(attr):
8384
break
85+
86+
def is_yaml_tag_none(tag):
87+
return isinstance(tag, ruamel.yaml.tag.Tag) and tag.value is None
88+
89+
if (tag is None or is_yaml_tag_none(tag)) and not default is None:
90+
tag = default
91+
8492
if tag is None:
8593
raise ValueError(
8694
f"No tag found for {value}. Either set the type field " "or use a YAML tag."
8795
)
88-
tag = str(tag)
89-
if tag.startswith("!"):
90-
tag = tag[1:]
91-
return tag
96+
else:
97+
tag = str(tag)
98+
if tag.startswith("!"):
99+
tag = tag[1:]
100+
return tag
92101

93102

94103
def _uninstantiable(cls):
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Each tensor is shaped by a set of ranks, denoted by capital letters
2+
# For example: Q is shaped by (B, M, H, E)
3+
# We'll use lower-case letters to index into the ranks
4+
# For example: Q[b, m, h, e] is the tensor Q at index (b, m, h, e)
5+
6+
# When making a projection list, it's equivalent to the Einsum subscript notation, so:
7+
# Q projection [b, m, h, e] means that b indexes into B, m indexes into M...
8+
# When making a projection dict, it's equivalent to the Einsum subscript/superscript notation, so:
9+
# K projection { B: b, M: p, H: h, E: e } means that b indexes into B, p indexes into M...
10+
11+
# Renames take a tensor name and turn them into a canonical name that we can use in
12+
# architecture constraints. For example, we want to use the words "input", "weight", and
13+
# "output" to refer to the tensors of an Einsum, but the Einsum QK has no clear "weight"
14+
# or "input" because both Q and K are inputs. So we rename K to be weight.
15+
16+
workload:
17+
rank_sizes:
18+
{% set BATCH_SIZE = BATCH_SIZE | default(1) %}
19+
{% set N_TOKENS = N_TOKENS | default(8192) %}
20+
B: {{BATCH_SIZE}}
21+
P: {{N_TOKENS}}
22+
M: {{N_TOKENS}}
23+
H: 32
24+
E: 128
25+
F: 128
26+
D: 4096 # = e * h
27+
C: 16384
28+
J: 4096
29+
G: 4096
30+
31+
bits_per_value: {All: 8}
32+
persistent_tensors: weight - Intermediates
33+
34+
einsums:
35+
- !Copy
36+
name: copy_I
37+
tensor: I
38+
- "V[b, m, h, e] = I[b, m, d] * WV[h, e, d]"
39+
- "K[b, m, h, e] = I[b, m, d] * WK[h, e, d]"
40+
- "Q[b, m, h, e] = I[b, m, d] * WQ[h, e, d]"
41+
42+
- einsum: "QK[b, m, p, h] = Q[b, m, h, e] * K[b, M: p, h, e]"
43+
renames: {input: Q}
44+
- "QK_softmax[b, m, p, h] = QK[b, m, p, h]"
45+
46+
- einsum: "AV[b, m, h, f] = QK_softmax[b, m, p, h] * V[b, M: p, h, E: f]"
47+
renames: {input: QK_softmax}
48+
- "Z[b, m, g] = AV[b, m, h, f] * WZ[h, f, g]"
49+
- "FFA[b, m, c] = Z[b, m, g] * WFFA[g, c]"
50+
- "FFB[b, m, j] = FFA[b, m, c] * WFFB[c, j]"
51+
52+
renames:
53+
einsums:
54+
- name: default
55+
tensor_accesses:
56+
- name: input
57+
source: Inputs & Intermediates if len(All) == 3 else Inputs
58+
expected_count: 1
59+
- name: output
60+
source: Outputs
61+
expected_count: 1
62+
- name: weight
63+
source: ~(input | output)
64+
expected_count: 1 if len(All) == 3 else 0

tests/test_adapter.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import unittest
2+
from pathlib import Path
3+
4+
import accelforge as af
5+
6+
INPUT_FILES_DIR = Path(__file__).parent / "input_files" / "adapters"
7+
8+
9+
class TestParsing(unittest.TestCase):
10+
def test_gpt3(self):
11+
spec = af.Spec.from_yaml(
12+
INPUT_FILES_DIR / "gpt3_6.7B.yaml"
13+
)
14+
self.assertEqual(spec.workload.einsum_names, ["V", "K", "Q", "QK_softmax", "Z", "FFA", "FFB"])
15+

0 commit comments

Comments
 (0)