phanerozoic commited on
Commit
70f26ab
·
verified ·
1 Parent(s): bd669ef

Uploaded using `kernel-builder`.

Browse files
build/torch211-cxx11-cpu-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """bitnet-cpu: ternary x INT8 GEMM for BitNet b1.58 (W1.58 A8) on CPUs.
2
+
3
+ Same 2-bit weight packing and API as phanerozoic/bitnet-tc (the CUDA member
4
+ of the stack); AVX-512 VNNI / AVX-VNNI / AVX2 paths selected at runtime,
5
+ with a portable scalar fallback.
6
+
7
+ High-level API:
8
+ pack_weights(W) ternary {-1,0,+1} int8 [N,K] -> packed uint8 [N,K//4]
9
+ quantize_activation(x) bf16/f32 [..,K] -> (int8, per-row bf16 scale)
10
+ bitnet_gemm(...) int8 activations x packed ternary weights -> bf16
11
+ bitnet_gemv_fused(...) bf16/f32 activations (fused quantize) -> bf16, M<16
12
+ bitnet_linear(x, w, sw) one-shot forward, auto-dispatches by M
13
+ BitLinear nn.Module wrapper
14
+ BitLinearKernel kernelize layer for transformers BitNet modules
15
+ """
16
+ from typing import Optional
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+
21
+ from ._ops import ops
22
+
23
+ _OK_DTYPES = (torch.bfloat16, torch.float32)
24
+
25
+
26
+ def pack_weights(W: torch.Tensor) -> torch.Tensor:
27
+ """Pack ternary weights {-1, 0, +1} into 2-bit codes, 4 per byte.
28
+
29
+ Encoding: -1 -> 1, 0 -> 2, +1 -> 3 (the kernel decodes via byte - 2).
30
+ Identical to the phanerozoic/bitnet-tc and Microsoft bitnet.cpp packing.
31
+
32
+ Args:
33
+ W: [N, K] int8 with values in {-1, 0, +1}.
34
+ Returns:
35
+ [N, K // 4] uint8.
36
+ """
37
+ assert W.dtype == torch.int8, f"W must be int8, got {W.dtype}"
38
+ assert W.dim() == 2, "W must be 2D"
39
+ N, K = W.shape
40
+ assert K % 4 == 0, f"K={K} must be a multiple of 4"
41
+ assert ((W >= -1) & (W <= 1)).all(), "W values must be in {-1, 0, +1}"
42
+ codes = (W.to(torch.int32) + 2).to(torch.uint8).view(N, K // 4, 4)
43
+ packed = (
44
+ codes[..., 0]
45
+ | (codes[..., 1] << 2)
46
+ | (codes[..., 2] << 4)
47
+ | (codes[..., 3] << 6)
48
+ ).contiguous()
49
+ return packed
50
+
51
+
52
+ def quantize_activation(x: torch.Tensor, eps: float = 1e-5):
53
+ """Per-token absmax INT8 quantization.
54
+
55
+ Args:
56
+ x: [..., K] bf16 or f32 tensor.
57
+ Returns:
58
+ (x_int8 [M, K], scale [M] bf16) where M is the flattened leading dim.
59
+ """
60
+ x_flat = x.reshape(-1, x.shape[-1]).contiguous()
61
+ M, K = x_flat.shape
62
+ if x_flat.dtype in _OK_DTYPES and x_flat.device.type == "cpu":
63
+ x_int8 = torch.empty((M, K), dtype=torch.int8)
64
+ scale = torch.empty((M,), dtype=torch.bfloat16)
65
+ ops.quantize_act(x_int8, scale, x_flat)
66
+ return x_int8, scale
67
+ absmax = x_flat.abs().amax(dim=-1, keepdim=True).clamp(min=eps)
68
+ scale = absmax / 127.0
69
+ x_int8 = (x_flat / scale).round().clamp(-127, 127).to(torch.int8)
70
+ return x_int8, scale.squeeze(-1).to(torch.bfloat16)
71
+
72
+
73
+ def bitnet_gemm(
74
+ x_int8: torch.Tensor,
75
+ w_packed: torch.Tensor,
76
+ scale_act: torch.Tensor,
77
+ scale_wt: torch.Tensor,
78
+ ) -> torch.Tensor:
79
+ """Ternary x INT8 GEMM. Returns [M, N] bf16."""
80
+ M, K = x_int8.shape
81
+ N = w_packed.shape[0]
82
+ out = torch.empty((M, N), dtype=torch.bfloat16)
83
+ ops.bitnet_gemm(out, x_int8, w_packed, scale_act, scale_wt, None)
84
+ return out
85
+
86
+
87
+ def bitnet_gemv_fused(
88
+ x: torch.Tensor,
89
+ w_packed: torch.Tensor,
90
+ scale_wt: torch.Tensor,
91
+ ) -> torch.Tensor:
92
+ """Fused quantize + ternary GEMV for M < 16. Returns [M, N] bf16."""
93
+ M, K = x.shape
94
+ N = w_packed.shape[0]
95
+ out = torch.empty((M, N), dtype=torch.bfloat16)
96
+ ops.bitnet_gemv_fused(out, x, w_packed, scale_wt)
97
+ return out
98
+
99
+
100
+ def bitnet_linear(
101
+ x: torch.Tensor,
102
+ w_packed: torch.Tensor,
103
+ scale_wt: torch.Tensor,
104
+ ) -> torch.Tensor:
105
+ """One-shot BitLinear forward. Auto-dispatches fused (M<16) vs split path.
106
+
107
+ Args:
108
+ x: [..., K] bf16 or f32.
109
+ w_packed: [N, K//4] uint8 packed ternary weights.
110
+ scale_wt: [N] bf16 per-output weight scale.
111
+ Returns:
112
+ [..., N] bf16.
113
+ """
114
+ assert x.dtype in _OK_DTYPES, f"x must be bf16 or f32, got {x.dtype}"
115
+ orig = x.shape
116
+ K = orig[-1]
117
+ x_flat = x.reshape(-1, K).contiguous()
118
+ M = x_flat.shape[0]
119
+ N = w_packed.shape[0]
120
+ if M < 16:
121
+ y = bitnet_gemv_fused(x_flat, w_packed, scale_wt)
122
+ else:
123
+ x_int8, scale_act = quantize_activation(x_flat)
124
+ y = bitnet_gemm(x_int8, w_packed, scale_act, scale_wt)
125
+ return y.view(*orig[:-1], N)
126
+
127
+
128
+ class BitLinear(nn.Module):
129
+ """Inference-only BitLinear: ternary weights, per-token INT8 activations."""
130
+
131
+ def __init__(self, in_features: int, out_features: int, bias: bool = False):
132
+ super().__init__()
133
+ assert in_features % 32 == 0, f"in_features={in_features} must be % 32"
134
+ self.in_features = in_features
135
+ self.out_features = out_features
136
+ self.register_buffer(
137
+ "w_packed", torch.zeros(out_features, in_features // 4, dtype=torch.uint8)
138
+ )
139
+ self.register_buffer(
140
+ "scale_wt", torch.ones(out_features, dtype=torch.bfloat16)
141
+ )
142
+ if bias:
143
+ self.bias = nn.Parameter(torch.zeros(out_features, dtype=torch.bfloat16))
144
+ else:
145
+ self.bias = None
146
+
147
+ @classmethod
148
+ def from_dense(cls, lin: nn.Linear) -> "BitLinear":
149
+ """Quantize a dense Linear via absmean ternarization (sanity-test only;
150
+ a real BitNet model is trained with QAT, not post-hoc quantized)."""
151
+ out_features, in_features = lin.weight.shape
152
+ bl = cls(in_features, out_features, bias=lin.bias is not None)
153
+ with torch.no_grad():
154
+ W = lin.weight.detach().float()
155
+ gamma = W.abs().mean(dim=-1, keepdim=True).clamp(min=1e-8)
156
+ Wq = (W / gamma).round().clamp(-1, 1).to(torch.int8)
157
+ bl.w_packed.copy_(pack_weights(Wq.cpu()))
158
+ bl.scale_wt.copy_(gamma.squeeze(-1).to(torch.bfloat16))
159
+ if bl.bias is not None:
160
+ bl.bias.copy_(lin.bias.detach().to(torch.bfloat16))
161
+ return bl
162
+
163
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
164
+ y = bitnet_linear(x, self.w_packed, self.scale_wt)
165
+ if self.bias is not None:
166
+ y = y + self.bias
167
+ return y
168
+
169
+
170
+ class BitLinearKernel(nn.Module):
171
+ """kernelize layer for the transformers BitNet linear modules.
172
+
173
+ kernelize binds this forward onto the host BitLinear / AutoBitLinear module,
174
+ so self is that module and the forward reads its attributes (weight,
175
+ weight_scale, rms_norm, bias). The ternary weight is converted to the
176
+ kernel's 2-bit layout once and cached; each call runs per-token INT8
177
+ quantization and the VNNI/AVX2 ternary GEMM.
178
+ """
179
+
180
+ can_torch_compile: bool = False
181
+
182
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
183
+ dt = input.dtype
184
+ rms = getattr(self, "rms_norm", None)
185
+ if rms is not None:
186
+ input = rms(input)
187
+
188
+ if not hasattr(self, "_bitnet_cpu_packed"):
189
+ w = self.weight
190
+ if w.dtype == torch.uint8:
191
+ # transformers stores ternary as int8 viewed through uint8 (255 -> -1).
192
+ tern = w.contiguous().view(torch.int8)
193
+ if tern.shape[0] != self.out_features: # bit-packed (out//4, in)
194
+ from transformers.integrations.bitnet import unpack_weights
195
+ tern = unpack_weights(w, dtype=torch.bfloat16)
196
+ tern = tern.round().clamp(-1, 1).to(torch.int8)
197
+ else:
198
+ g = w.detach().float().abs().mean().clamp(min=1e-5)
199
+ tern = (w.detach().float() / g).round().clamp(-1, 1).to(torch.int8)
200
+ self._bitnet_cpu_n = tern.shape[0]
201
+ self._bitnet_cpu_packed = pack_weights(tern.contiguous().cpu())
202
+ ws = getattr(self, "weight_scale", None)
203
+ if ws is not None:
204
+ s = ws.detach().float().reshape(-1)
205
+ s = s.expand(self._bitnet_cpu_n) if s.numel() == 1 else s
206
+ else:
207
+ s = w.detach().float().abs().mean().clamp(min=1e-5).reshape(1).expand(self._bitnet_cpu_n)
208
+ self._bitnet_cpu_sw = s.to(torch.bfloat16).contiguous().cpu()
209
+
210
+ shp = input.shape
211
+ x = input.reshape(-1, shp[-1])
212
+ if x.dtype not in _OK_DTYPES:
213
+ x = x.to(torch.float32)
214
+ x = x.contiguous()
215
+ out = bitnet_linear(x, self._bitnet_cpu_packed, self._bitnet_cpu_sw)
216
+ if getattr(self, "bias", None) is not None:
217
+ out = out + self.bias
218
+ return out.reshape(*shp[:-1], self._bitnet_cpu_n).to(dt)
219
+
220
+
221
+ __all__ = [
222
+ "pack_weights",
223
+ "quantize_activation",
224
+ "bitnet_gemm",
225
+ "bitnet_gemv_fused",
226
+ "bitnet_linear",
227
+ "BitLinear",
228
+ "BitLinearKernel",
229
+ ]
build/torch211-cxx11-cpu-x86_64-linux/_bitnet_cpu_cpu_bd669ef.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f1d801db47bf2cebce1da4617dc0bb6e515830d0748587400612b2e87de30542
3
+ size 91568
build/torch211-cxx11-cpu-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _bitnet_cpu_cpu_bd669ef
3
+ ops = torch.ops._bitnet_cpu_cpu_bd669ef
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_bitnet_cpu_cpu_bd669ef::{op_name}"
build/torch211-cxx11-cpu-x86_64-linux/bitnet_cpu/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch211-cxx11-cpu-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bitnet-cpu",
3
+ "id": "_bitnet_cpu_cpu_bd669ef",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cpu"
9
+ },
10
+ "digest": {
11
+ "algorithm": "sha256",
12
+ "files": {
13
+ "__init__.py": "ipCv02rtmMOwCw75aWDraJag/on6FtbuuKkYLxWUgiA=",
14
+ "_bitnet_cpu_cpu_bd669ef.abi3.so": "8dgB20e/LOvOHaRhfcC7blFYMNB0hYdABhKy6H3jBUI=",
15
+ "_ops.py": "CpD3fRDWoqe63dtPPstdFtlIPKs13yPdbOGpg4aH2lk=",
16
+ "bitnet_cpu/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
17
+ }
18
+ },
19
+ "provenance": {
20
+ "kernel-builder": {
21
+ "version": "0.17.0-dev0",
22
+ "sha": "2c516fc6678570c013319adeb64605fc8e8d5394",
23
+ "dirty": false
24
+ },
25
+ "kernel": {
26
+ "sha": "bd669ef68e49c5873a7810015ce6a9503fed5394",
27
+ "dirty": false
28
+ }
29
+ }
30
+ }
build/torch212-cxx11-cpu-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """bitnet-cpu: ternary x INT8 GEMM for BitNet b1.58 (W1.58 A8) on CPUs.
2
+
3
+ Same 2-bit weight packing and API as phanerozoic/bitnet-tc (the CUDA member
4
+ of the stack); AVX-512 VNNI / AVX-VNNI / AVX2 paths selected at runtime,
5
+ with a portable scalar fallback.
6
+
7
+ High-level API:
8
+ pack_weights(W) ternary {-1,0,+1} int8 [N,K] -> packed uint8 [N,K//4]
9
+ quantize_activation(x) bf16/f32 [..,K] -> (int8, per-row bf16 scale)
10
+ bitnet_gemm(...) int8 activations x packed ternary weights -> bf16
11
+ bitnet_gemv_fused(...) bf16/f32 activations (fused quantize) -> bf16, M<16
12
+ bitnet_linear(x, w, sw) one-shot forward, auto-dispatches by M
13
+ BitLinear nn.Module wrapper
14
+ BitLinearKernel kernelize layer for transformers BitNet modules
15
+ """
16
+ from typing import Optional
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+
21
+ from ._ops import ops
22
+
23
+ _OK_DTYPES = (torch.bfloat16, torch.float32)
24
+
25
+
26
+ def pack_weights(W: torch.Tensor) -> torch.Tensor:
27
+ """Pack ternary weights {-1, 0, +1} into 2-bit codes, 4 per byte.
28
+
29
+ Encoding: -1 -> 1, 0 -> 2, +1 -> 3 (the kernel decodes via byte - 2).
30
+ Identical to the phanerozoic/bitnet-tc and Microsoft bitnet.cpp packing.
31
+
32
+ Args:
33
+ W: [N, K] int8 with values in {-1, 0, +1}.
34
+ Returns:
35
+ [N, K // 4] uint8.
36
+ """
37
+ assert W.dtype == torch.int8, f"W must be int8, got {W.dtype}"
38
+ assert W.dim() == 2, "W must be 2D"
39
+ N, K = W.shape
40
+ assert K % 4 == 0, f"K={K} must be a multiple of 4"
41
+ assert ((W >= -1) & (W <= 1)).all(), "W values must be in {-1, 0, +1}"
42
+ codes = (W.to(torch.int32) + 2).to(torch.uint8).view(N, K // 4, 4)
43
+ packed = (
44
+ codes[..., 0]
45
+ | (codes[..., 1] << 2)
46
+ | (codes[..., 2] << 4)
47
+ | (codes[..., 3] << 6)
48
+ ).contiguous()
49
+ return packed
50
+
51
+
52
+ def quantize_activation(x: torch.Tensor, eps: float = 1e-5):
53
+ """Per-token absmax INT8 quantization.
54
+
55
+ Args:
56
+ x: [..., K] bf16 or f32 tensor.
57
+ Returns:
58
+ (x_int8 [M, K], scale [M] bf16) where M is the flattened leading dim.
59
+ """
60
+ x_flat = x.reshape(-1, x.shape[-1]).contiguous()
61
+ M, K = x_flat.shape
62
+ if x_flat.dtype in _OK_DTYPES and x_flat.device.type == "cpu":
63
+ x_int8 = torch.empty((M, K), dtype=torch.int8)
64
+ scale = torch.empty((M,), dtype=torch.bfloat16)
65
+ ops.quantize_act(x_int8, scale, x_flat)
66
+ return x_int8, scale
67
+ absmax = x_flat.abs().amax(dim=-1, keepdim=True).clamp(min=eps)
68
+ scale = absmax / 127.0
69
+ x_int8 = (x_flat / scale).round().clamp(-127, 127).to(torch.int8)
70
+ return x_int8, scale.squeeze(-1).to(torch.bfloat16)
71
+
72
+
73
+ def bitnet_gemm(
74
+ x_int8: torch.Tensor,
75
+ w_packed: torch.Tensor,
76
+ scale_act: torch.Tensor,
77
+ scale_wt: torch.Tensor,
78
+ ) -> torch.Tensor:
79
+ """Ternary x INT8 GEMM. Returns [M, N] bf16."""
80
+ M, K = x_int8.shape
81
+ N = w_packed.shape[0]
82
+ out = torch.empty((M, N), dtype=torch.bfloat16)
83
+ ops.bitnet_gemm(out, x_int8, w_packed, scale_act, scale_wt, None)
84
+ return out
85
+
86
+
87
+ def bitnet_gemv_fused(
88
+ x: torch.Tensor,
89
+ w_packed: torch.Tensor,
90
+ scale_wt: torch.Tensor,
91
+ ) -> torch.Tensor:
92
+ """Fused quantize + ternary GEMV for M < 16. Returns [M, N] bf16."""
93
+ M, K = x.shape
94
+ N = w_packed.shape[0]
95
+ out = torch.empty((M, N), dtype=torch.bfloat16)
96
+ ops.bitnet_gemv_fused(out, x, w_packed, scale_wt)
97
+ return out
98
+
99
+
100
+ def bitnet_linear(
101
+ x: torch.Tensor,
102
+ w_packed: torch.Tensor,
103
+ scale_wt: torch.Tensor,
104
+ ) -> torch.Tensor:
105
+ """One-shot BitLinear forward. Auto-dispatches fused (M<16) vs split path.
106
+
107
+ Args:
108
+ x: [..., K] bf16 or f32.
109
+ w_packed: [N, K//4] uint8 packed ternary weights.
110
+ scale_wt: [N] bf16 per-output weight scale.
111
+ Returns:
112
+ [..., N] bf16.
113
+ """
114
+ assert x.dtype in _OK_DTYPES, f"x must be bf16 or f32, got {x.dtype}"
115
+ orig = x.shape
116
+ K = orig[-1]
117
+ x_flat = x.reshape(-1, K).contiguous()
118
+ M = x_flat.shape[0]
119
+ N = w_packed.shape[0]
120
+ if M < 16:
121
+ y = bitnet_gemv_fused(x_flat, w_packed, scale_wt)
122
+ else:
123
+ x_int8, scale_act = quantize_activation(x_flat)
124
+ y = bitnet_gemm(x_int8, w_packed, scale_act, scale_wt)
125
+ return y.view(*orig[:-1], N)
126
+
127
+
128
+ class BitLinear(nn.Module):
129
+ """Inference-only BitLinear: ternary weights, per-token INT8 activations."""
130
+
131
+ def __init__(self, in_features: int, out_features: int, bias: bool = False):
132
+ super().__init__()
133
+ assert in_features % 32 == 0, f"in_features={in_features} must be % 32"
134
+ self.in_features = in_features
135
+ self.out_features = out_features
136
+ self.register_buffer(
137
+ "w_packed", torch.zeros(out_features, in_features // 4, dtype=torch.uint8)
138
+ )
139
+ self.register_buffer(
140
+ "scale_wt", torch.ones(out_features, dtype=torch.bfloat16)
141
+ )
142
+ if bias:
143
+ self.bias = nn.Parameter(torch.zeros(out_features, dtype=torch.bfloat16))
144
+ else:
145
+ self.bias = None
146
+
147
+ @classmethod
148
+ def from_dense(cls, lin: nn.Linear) -> "BitLinear":
149
+ """Quantize a dense Linear via absmean ternarization (sanity-test only;
150
+ a real BitNet model is trained with QAT, not post-hoc quantized)."""
151
+ out_features, in_features = lin.weight.shape
152
+ bl = cls(in_features, out_features, bias=lin.bias is not None)
153
+ with torch.no_grad():
154
+ W = lin.weight.detach().float()
155
+ gamma = W.abs().mean(dim=-1, keepdim=True).clamp(min=1e-8)
156
+ Wq = (W / gamma).round().clamp(-1, 1).to(torch.int8)
157
+ bl.w_packed.copy_(pack_weights(Wq.cpu()))
158
+ bl.scale_wt.copy_(gamma.squeeze(-1).to(torch.bfloat16))
159
+ if bl.bias is not None:
160
+ bl.bias.copy_(lin.bias.detach().to(torch.bfloat16))
161
+ return bl
162
+
163
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
164
+ y = bitnet_linear(x, self.w_packed, self.scale_wt)
165
+ if self.bias is not None:
166
+ y = y + self.bias
167
+ return y
168
+
169
+
170
+ class BitLinearKernel(nn.Module):
171
+ """kernelize layer for the transformers BitNet linear modules.
172
+
173
+ kernelize binds this forward onto the host BitLinear / AutoBitLinear module,
174
+ so self is that module and the forward reads its attributes (weight,
175
+ weight_scale, rms_norm, bias). The ternary weight is converted to the
176
+ kernel's 2-bit layout once and cached; each call runs per-token INT8
177
+ quantization and the VNNI/AVX2 ternary GEMM.
178
+ """
179
+
180
+ can_torch_compile: bool = False
181
+
182
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
183
+ dt = input.dtype
184
+ rms = getattr(self, "rms_norm", None)
185
+ if rms is not None:
186
+ input = rms(input)
187
+
188
+ if not hasattr(self, "_bitnet_cpu_packed"):
189
+ w = self.weight
190
+ if w.dtype == torch.uint8:
191
+ # transformers stores ternary as int8 viewed through uint8 (255 -> -1).
192
+ tern = w.contiguous().view(torch.int8)
193
+ if tern.shape[0] != self.out_features: # bit-packed (out//4, in)
194
+ from transformers.integrations.bitnet import unpack_weights
195
+ tern = unpack_weights(w, dtype=torch.bfloat16)
196
+ tern = tern.round().clamp(-1, 1).to(torch.int8)
197
+ else:
198
+ g = w.detach().float().abs().mean().clamp(min=1e-5)
199
+ tern = (w.detach().float() / g).round().clamp(-1, 1).to(torch.int8)
200
+ self._bitnet_cpu_n = tern.shape[0]
201
+ self._bitnet_cpu_packed = pack_weights(tern.contiguous().cpu())
202
+ ws = getattr(self, "weight_scale", None)
203
+ if ws is not None:
204
+ s = ws.detach().float().reshape(-1)
205
+ s = s.expand(self._bitnet_cpu_n) if s.numel() == 1 else s
206
+ else:
207
+ s = w.detach().float().abs().mean().clamp(min=1e-5).reshape(1).expand(self._bitnet_cpu_n)
208
+ self._bitnet_cpu_sw = s.to(torch.bfloat16).contiguous().cpu()
209
+
210
+ shp = input.shape
211
+ x = input.reshape(-1, shp[-1])
212
+ if x.dtype not in _OK_DTYPES:
213
+ x = x.to(torch.float32)
214
+ x = x.contiguous()
215
+ out = bitnet_linear(x, self._bitnet_cpu_packed, self._bitnet_cpu_sw)
216
+ if getattr(self, "bias", None) is not None:
217
+ out = out + self.bias
218
+ return out.reshape(*shp[:-1], self._bitnet_cpu_n).to(dt)
219
+
220
+
221
+ __all__ = [
222
+ "pack_weights",
223
+ "quantize_activation",
224
+ "bitnet_gemm",
225
+ "bitnet_gemv_fused",
226
+ "bitnet_linear",
227
+ "BitLinear",
228
+ "BitLinearKernel",
229
+ ]
build/torch212-cxx11-cpu-x86_64-linux/_bitnet_cpu_cpu_bd669ef.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:785e2eaef191ee927b27634d64b92b960ae004d20e2c287cad1ec758e03b50e0
3
+ size 91392
build/torch212-cxx11-cpu-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _bitnet_cpu_cpu_bd669ef
3
+ ops = torch.ops._bitnet_cpu_cpu_bd669ef
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_bitnet_cpu_cpu_bd669ef::{op_name}"
build/torch212-cxx11-cpu-x86_64-linux/bitnet_cpu/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch212-cxx11-cpu-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bitnet-cpu",
3
+ "id": "_bitnet_cpu_cpu_bd669ef",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cpu"
9
+ },
10
+ "digest": {
11
+ "algorithm": "sha256",
12
+ "files": {
13
+ "__init__.py": "ipCv02rtmMOwCw75aWDraJag/on6FtbuuKkYLxWUgiA=",
14
+ "_bitnet_cpu_cpu_bd669ef.abi3.so": "eF4urvGR7pJ7J2NNZLkrlgrgBNIOLCh8rR7HWOA7UOA=",
15
+ "_ops.py": "CpD3fRDWoqe63dtPPstdFtlIPKs13yPdbOGpg4aH2lk=",
16
+ "bitnet_cpu/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
17
+ }
18
+ },
19
+ "provenance": {
20
+ "kernel-builder": {
21
+ "version": "0.17.0-dev0",
22
+ "sha": "2c516fc6678570c013319adeb64605fc8e8d5394",
23
+ "dirty": false
24
+ },
25
+ "kernel": {
26
+ "sha": "bd669ef68e49c5873a7810015ce6a9503fed5394",
27
+ "dirty": false
28
+ }
29
+ }
30
+ }
build/torch213-cxx11-cpu-x86_64-linux/__init__.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """bitnet-cpu: ternary x INT8 GEMM for BitNet b1.58 (W1.58 A8) on CPUs.
2
+
3
+ Same 2-bit weight packing and API as phanerozoic/bitnet-tc (the CUDA member
4
+ of the stack); AVX-512 VNNI / AVX-VNNI / AVX2 paths selected at runtime,
5
+ with a portable scalar fallback.
6
+
7
+ High-level API:
8
+ pack_weights(W) ternary {-1,0,+1} int8 [N,K] -> packed uint8 [N,K//4]
9
+ quantize_activation(x) bf16/f32 [..,K] -> (int8, per-row bf16 scale)
10
+ bitnet_gemm(...) int8 activations x packed ternary weights -> bf16
11
+ bitnet_gemv_fused(...) bf16/f32 activations (fused quantize) -> bf16, M<16
12
+ bitnet_linear(x, w, sw) one-shot forward, auto-dispatches by M
13
+ BitLinear nn.Module wrapper
14
+ BitLinearKernel kernelize layer for transformers BitNet modules
15
+ """
16
+ from typing import Optional
17
+
18
+ import torch
19
+ import torch.nn as nn
20
+
21
+ from ._ops import ops
22
+
23
+ _OK_DTYPES = (torch.bfloat16, torch.float32)
24
+
25
+
26
+ def pack_weights(W: torch.Tensor) -> torch.Tensor:
27
+ """Pack ternary weights {-1, 0, +1} into 2-bit codes, 4 per byte.
28
+
29
+ Encoding: -1 -> 1, 0 -> 2, +1 -> 3 (the kernel decodes via byte - 2).
30
+ Identical to the phanerozoic/bitnet-tc and Microsoft bitnet.cpp packing.
31
+
32
+ Args:
33
+ W: [N, K] int8 with values in {-1, 0, +1}.
34
+ Returns:
35
+ [N, K // 4] uint8.
36
+ """
37
+ assert W.dtype == torch.int8, f"W must be int8, got {W.dtype}"
38
+ assert W.dim() == 2, "W must be 2D"
39
+ N, K = W.shape
40
+ assert K % 4 == 0, f"K={K} must be a multiple of 4"
41
+ assert ((W >= -1) & (W <= 1)).all(), "W values must be in {-1, 0, +1}"
42
+ codes = (W.to(torch.int32) + 2).to(torch.uint8).view(N, K // 4, 4)
43
+ packed = (
44
+ codes[..., 0]
45
+ | (codes[..., 1] << 2)
46
+ | (codes[..., 2] << 4)
47
+ | (codes[..., 3] << 6)
48
+ ).contiguous()
49
+ return packed
50
+
51
+
52
+ def quantize_activation(x: torch.Tensor, eps: float = 1e-5):
53
+ """Per-token absmax INT8 quantization.
54
+
55
+ Args:
56
+ x: [..., K] bf16 or f32 tensor.
57
+ Returns:
58
+ (x_int8 [M, K], scale [M] bf16) where M is the flattened leading dim.
59
+ """
60
+ x_flat = x.reshape(-1, x.shape[-1]).contiguous()
61
+ M, K = x_flat.shape
62
+ if x_flat.dtype in _OK_DTYPES and x_flat.device.type == "cpu":
63
+ x_int8 = torch.empty((M, K), dtype=torch.int8)
64
+ scale = torch.empty((M,), dtype=torch.bfloat16)
65
+ ops.quantize_act(x_int8, scale, x_flat)
66
+ return x_int8, scale
67
+ absmax = x_flat.abs().amax(dim=-1, keepdim=True).clamp(min=eps)
68
+ scale = absmax / 127.0
69
+ x_int8 = (x_flat / scale).round().clamp(-127, 127).to(torch.int8)
70
+ return x_int8, scale.squeeze(-1).to(torch.bfloat16)
71
+
72
+
73
+ def bitnet_gemm(
74
+ x_int8: torch.Tensor,
75
+ w_packed: torch.Tensor,
76
+ scale_act: torch.Tensor,
77
+ scale_wt: torch.Tensor,
78
+ ) -> torch.Tensor:
79
+ """Ternary x INT8 GEMM. Returns [M, N] bf16."""
80
+ M, K = x_int8.shape
81
+ N = w_packed.shape[0]
82
+ out = torch.empty((M, N), dtype=torch.bfloat16)
83
+ ops.bitnet_gemm(out, x_int8, w_packed, scale_act, scale_wt, None)
84
+ return out
85
+
86
+
87
+ def bitnet_gemv_fused(
88
+ x: torch.Tensor,
89
+ w_packed: torch.Tensor,
90
+ scale_wt: torch.Tensor,
91
+ ) -> torch.Tensor:
92
+ """Fused quantize + ternary GEMV for M < 16. Returns [M, N] bf16."""
93
+ M, K = x.shape
94
+ N = w_packed.shape[0]
95
+ out = torch.empty((M, N), dtype=torch.bfloat16)
96
+ ops.bitnet_gemv_fused(out, x, w_packed, scale_wt)
97
+ return out
98
+
99
+
100
+ def bitnet_linear(
101
+ x: torch.Tensor,
102
+ w_packed: torch.Tensor,
103
+ scale_wt: torch.Tensor,
104
+ ) -> torch.Tensor:
105
+ """One-shot BitLinear forward. Auto-dispatches fused (M<16) vs split path.
106
+
107
+ Args:
108
+ x: [..., K] bf16 or f32.
109
+ w_packed: [N, K//4] uint8 packed ternary weights.
110
+ scale_wt: [N] bf16 per-output weight scale.
111
+ Returns:
112
+ [..., N] bf16.
113
+ """
114
+ assert x.dtype in _OK_DTYPES, f"x must be bf16 or f32, got {x.dtype}"
115
+ orig = x.shape
116
+ K = orig[-1]
117
+ x_flat = x.reshape(-1, K).contiguous()
118
+ M = x_flat.shape[0]
119
+ N = w_packed.shape[0]
120
+ if M < 16:
121
+ y = bitnet_gemv_fused(x_flat, w_packed, scale_wt)
122
+ else:
123
+ x_int8, scale_act = quantize_activation(x_flat)
124
+ y = bitnet_gemm(x_int8, w_packed, scale_act, scale_wt)
125
+ return y.view(*orig[:-1], N)
126
+
127
+
128
+ class BitLinear(nn.Module):
129
+ """Inference-only BitLinear: ternary weights, per-token INT8 activations."""
130
+
131
+ def __init__(self, in_features: int, out_features: int, bias: bool = False):
132
+ super().__init__()
133
+ assert in_features % 32 == 0, f"in_features={in_features} must be % 32"
134
+ self.in_features = in_features
135
+ self.out_features = out_features
136
+ self.register_buffer(
137
+ "w_packed", torch.zeros(out_features, in_features // 4, dtype=torch.uint8)
138
+ )
139
+ self.register_buffer(
140
+ "scale_wt", torch.ones(out_features, dtype=torch.bfloat16)
141
+ )
142
+ if bias:
143
+ self.bias = nn.Parameter(torch.zeros(out_features, dtype=torch.bfloat16))
144
+ else:
145
+ self.bias = None
146
+
147
+ @classmethod
148
+ def from_dense(cls, lin: nn.Linear) -> "BitLinear":
149
+ """Quantize a dense Linear via absmean ternarization (sanity-test only;
150
+ a real BitNet model is trained with QAT, not post-hoc quantized)."""
151
+ out_features, in_features = lin.weight.shape
152
+ bl = cls(in_features, out_features, bias=lin.bias is not None)
153
+ with torch.no_grad():
154
+ W = lin.weight.detach().float()
155
+ gamma = W.abs().mean(dim=-1, keepdim=True).clamp(min=1e-8)
156
+ Wq = (W / gamma).round().clamp(-1, 1).to(torch.int8)
157
+ bl.w_packed.copy_(pack_weights(Wq.cpu()))
158
+ bl.scale_wt.copy_(gamma.squeeze(-1).to(torch.bfloat16))
159
+ if bl.bias is not None:
160
+ bl.bias.copy_(lin.bias.detach().to(torch.bfloat16))
161
+ return bl
162
+
163
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
164
+ y = bitnet_linear(x, self.w_packed, self.scale_wt)
165
+ if self.bias is not None:
166
+ y = y + self.bias
167
+ return y
168
+
169
+
170
+ class BitLinearKernel(nn.Module):
171
+ """kernelize layer for the transformers BitNet linear modules.
172
+
173
+ kernelize binds this forward onto the host BitLinear / AutoBitLinear module,
174
+ so self is that module and the forward reads its attributes (weight,
175
+ weight_scale, rms_norm, bias). The ternary weight is converted to the
176
+ kernel's 2-bit layout once and cached; each call runs per-token INT8
177
+ quantization and the VNNI/AVX2 ternary GEMM.
178
+ """
179
+
180
+ can_torch_compile: bool = False
181
+
182
+ def forward(self, input: torch.Tensor) -> torch.Tensor:
183
+ dt = input.dtype
184
+ rms = getattr(self, "rms_norm", None)
185
+ if rms is not None:
186
+ input = rms(input)
187
+
188
+ if not hasattr(self, "_bitnet_cpu_packed"):
189
+ w = self.weight
190
+ if w.dtype == torch.uint8:
191
+ # transformers stores ternary as int8 viewed through uint8 (255 -> -1).
192
+ tern = w.contiguous().view(torch.int8)
193
+ if tern.shape[0] != self.out_features: # bit-packed (out//4, in)
194
+ from transformers.integrations.bitnet import unpack_weights
195
+ tern = unpack_weights(w, dtype=torch.bfloat16)
196
+ tern = tern.round().clamp(-1, 1).to(torch.int8)
197
+ else:
198
+ g = w.detach().float().abs().mean().clamp(min=1e-5)
199
+ tern = (w.detach().float() / g).round().clamp(-1, 1).to(torch.int8)
200
+ self._bitnet_cpu_n = tern.shape[0]
201
+ self._bitnet_cpu_packed = pack_weights(tern.contiguous().cpu())
202
+ ws = getattr(self, "weight_scale", None)
203
+ if ws is not None:
204
+ s = ws.detach().float().reshape(-1)
205
+ s = s.expand(self._bitnet_cpu_n) if s.numel() == 1 else s
206
+ else:
207
+ s = w.detach().float().abs().mean().clamp(min=1e-5).reshape(1).expand(self._bitnet_cpu_n)
208
+ self._bitnet_cpu_sw = s.to(torch.bfloat16).contiguous().cpu()
209
+
210
+ shp = input.shape
211
+ x = input.reshape(-1, shp[-1])
212
+ if x.dtype not in _OK_DTYPES:
213
+ x = x.to(torch.float32)
214
+ x = x.contiguous()
215
+ out = bitnet_linear(x, self._bitnet_cpu_packed, self._bitnet_cpu_sw)
216
+ if getattr(self, "bias", None) is not None:
217
+ out = out + self.bias
218
+ return out.reshape(*shp[:-1], self._bitnet_cpu_n).to(dt)
219
+
220
+
221
+ __all__ = [
222
+ "pack_weights",
223
+ "quantize_activation",
224
+ "bitnet_gemm",
225
+ "bitnet_gemv_fused",
226
+ "bitnet_linear",
227
+ "BitLinear",
228
+ "BitLinearKernel",
229
+ ]
build/torch213-cxx11-cpu-x86_64-linux/_bitnet_cpu_cpu_bd669ef.abi3.so ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c0f2f6bc73bacb62e645fad52e4f61fb680e4f718a8acbcea27351c4596d069e
3
+ size 91232
build/torch213-cxx11-cpu-x86_64-linux/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _bitnet_cpu_cpu_bd669ef
3
+ ops = torch.ops._bitnet_cpu_cpu_bd669ef
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_bitnet_cpu_cpu_bd669ef::{op_name}"
build/torch213-cxx11-cpu-x86_64-linux/bitnet_cpu/__init__.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import ctypes
2
+ import importlib.util
3
+ import sys
4
+ from pathlib import Path
5
+ from types import ModuleType
6
+
7
+
8
+ def _import_from_path(file_path: Path) -> ModuleType:
9
+ # We cannot use the module name as-is, after adding it to `sys.modules`,
10
+ # it would also be used for other imports. So, we make a module name that
11
+ # depends on the path for it to be unique using the hex-encoded hash of
12
+ # the path.
13
+ path_hash = "{:x}".format(ctypes.c_size_t(hash(file_path.absolute())).value)
14
+ module_name = path_hash
15
+ spec = importlib.util.spec_from_file_location(module_name, file_path)
16
+ if spec is None:
17
+ raise ImportError(f"Cannot load spec for {module_name} from {file_path}")
18
+ module = importlib.util.module_from_spec(spec)
19
+ if module is None:
20
+ raise ImportError(f"Cannot load module {module_name} from spec")
21
+ sys.modules[module_name] = module
22
+ spec.loader.exec_module(module) # type: ignore
23
+ return module
24
+
25
+
26
+ globals().update(vars(_import_from_path(Path(__file__).parent.parent / "__init__.py")))
build/torch213-cxx11-cpu-x86_64-linux/metadata.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "bitnet-cpu",
3
+ "id": "_bitnet_cpu_cpu_bd669ef",
4
+ "version": 1,
5
+ "license": "Apache-2.0",
6
+ "python-depends": [],
7
+ "backend": {
8
+ "type": "cpu"
9
+ },
10
+ "digest": {
11
+ "algorithm": "sha256",
12
+ "files": {
13
+ "__init__.py": "ipCv02rtmMOwCw75aWDraJag/on6FtbuuKkYLxWUgiA=",
14
+ "_bitnet_cpu_cpu_bd669ef.abi3.so": "wPL2vHO6y2LmRfrVLk9h+2gOT3GKisvOonNRxFltBp4=",
15
+ "_ops.py": "CpD3fRDWoqe63dtPPstdFtlIPKs13yPdbOGpg4aH2lk=",
16
+ "bitnet_cpu/__init__.py": "DFYPlrhXwYjEqCl/8n0SmWGZV8NFml5DPhMjKfv98GY="
17
+ }
18
+ },
19
+ "provenance": {
20
+ "kernel-builder": {
21
+ "version": "0.17.0-dev0",
22
+ "sha": "2c516fc6678570c013319adeb64605fc8e8d5394",
23
+ "dirty": false
24
+ },
25
+ "kernel": {
26
+ "sha": "bd669ef68e49c5873a7810015ce6a9503fed5394",
27
+ "dirty": false
28
+ }
29
+ }
30
+ }