tardits 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tardits/gen.py ADDED
@@ -0,0 +1,150 @@
1
+ from argparse import ArgumentParser
2
+ import sys
3
+ import time
4
+ import os
5
+ import yaml
6
+
7
+ import torch
8
+ from tardits.models import HyperFormer, RealGPT
9
+ from tardits.modules import get_device
10
+
11
+
12
+ def main():
13
+ # --- CLI Arguments ---
14
+ parser = ArgumentParser(
15
+ description="Generates text from a trained model using its YAML config"
16
+ )
17
+
18
+ parser.add_argument(
19
+ "--config",
20
+ "-c",
21
+ type=str,
22
+ default="examples/configs/complex.yaml",
23
+ help="Path to YAML configuration file",
24
+ )
25
+ parser.add_argument(
26
+ "--checkpoint",
27
+ type=str,
28
+ default=None,
29
+ help="Optional override for model checkpoint path",
30
+ )
31
+ parser.add_argument(
32
+ "--vocab",
33
+ type=str,
34
+ default=None,
35
+ help="Optional override for vocab path",
36
+ )
37
+ parser.add_argument(
38
+ "--prompt",
39
+ "-p",
40
+ type=str,
41
+ default="\n",
42
+ help="Text prompt to start generation",
43
+ )
44
+ parser.add_argument(
45
+ "--length",
46
+ "-l",
47
+ type=int,
48
+ default=500,
49
+ help="Number of tokens to generate",
50
+ )
51
+ parser.add_argument(
52
+ "--temp",
53
+ "-t",
54
+ type=float,
55
+ default=1.0,
56
+ help="Model sampling temperature",
57
+ )
58
+ parser.add_argument(
59
+ "--top_k",
60
+ "-k",
61
+ type=int,
62
+ default=10,
63
+ help="Top-k sampling threshold",
64
+ )
65
+ parser.add_argument(
66
+ "--follow",
67
+ "-f",
68
+ action="store_true",
69
+ help="Endless stream of tokens",
70
+ )
71
+ parser.add_argument(
72
+ "--delay",
73
+ type=float,
74
+ default=0.04,
75
+ help="Delay between tokens in follow mode (seconds)",
76
+ )
77
+
78
+ args = parser.parse_args()
79
+
80
+ # --- Read Config File ---
81
+ with open(args.config, "r", encoding="utf-8") as f:
82
+ cfg = yaml.safe_load(f)
83
+
84
+ if cfg["model"]["level"] < 1:
85
+ GPT = RealGPT
86
+ else:
87
+ GPT = HyperFormer
88
+
89
+ # --- Load Config ---
90
+ checkpoint_path = args.checkpoint or cfg["trainer"]["checkpoint"]
91
+ vocab_path = args.vocab or cfg.get("data", {}).get(
92
+ "vocab_save_path", "examples/saved/chars.txt"
93
+ )
94
+ device = get_device(cfg.get("device", "auto"))
95
+
96
+ # --- Load Weights ---
97
+ model = GPT.load_from_checkpoint(checkpoint_path, device=device)
98
+ model.eval()
99
+
100
+ # --- Load Vocabulary ---
101
+ with open(vocab_path, "r", encoding="utf-8") as ch:
102
+ chars = list(ch.read())
103
+
104
+ stoi = {ch: i for i, ch in enumerate(chars)}
105
+ itos = {i: ch for i, ch in enumerate(chars)}
106
+
107
+ def encode(si):
108
+ return [stoi[c] for c in si]
109
+
110
+ def decode(li):
111
+ return "".join([itos[i] for i in li])
112
+
113
+ prompt = args.prompt.replace("\\n", "\n")
114
+ idx = torch.tensor([encode(prompt)], dtype=torch.long).to(device)
115
+
116
+ # --- Generation ---
117
+ if args.follow:
118
+ # ๐Ÿงน Clear terminal screen
119
+ print("\033[2J\033[H", end="")
120
+ print(
121
+ f"โณ Token-Stream (complexity-lvl: {cfg['model']['level']} @ {checkpoint_path}) โ€” Ctrl+C to exit\n"
122
+ )
123
+ try:
124
+ sys.stdout.write(prompt)
125
+ sys.stdout.flush()
126
+
127
+ for token_id in model.generate_stream(
128
+ idx, temp=args.temp, top_k=args.top_k
129
+ ):
130
+ char = itos[int(token_id)]
131
+ sys.stdout.write(char)
132
+ sys.stdout.flush()
133
+
134
+ if args.delay > 0:
135
+ time.sleep(args.delay)
136
+ except KeyboardInterrupt:
137
+ print("\n\n๐Ÿ›‘ Token stream stopped.")
138
+
139
+ else:
140
+ print(
141
+ decode(
142
+ model.generate(
143
+ idx, max_new_tokens=args.length, temp=args.temp, top_k=args.top_k
144
+ )[0].tolist()
145
+ )
146
+ )
147
+
148
+
149
+ if __name__ == "__main__":
150
+ main()
@@ -0,0 +1,4 @@
1
+ from .hyper import HyperFormer
2
+ from .real import RealGPT
3
+
4
+ __all__ = ["HyperFormer", "RealGPT"]
@@ -0,0 +1,144 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch.nn import functional as F
4
+
5
+ from tardits.modules import (
6
+ HyperEmbedding,
7
+ HyperLMHead,
8
+ HyperRMSNorm,
9
+ SAIL,
10
+ GyRoPE,
11
+ HyperLinear,
12
+ LMCoreMixin
13
+ )
14
+ from tardits.modules.conf import ModelConfig
15
+
16
+
17
+ class HyperSelfAttention(nn.Module):
18
+ """
19
+ Universal multi-headed self-attention for 2^level hypercomplex spaces.
20
+ """
21
+
22
+ def __init__(self, config: ModelConfig):
23
+ super().__init__()
24
+ self.config = config
25
+ self.level = config.level
26
+ self.head_size = config.n_embd // config.n_head
27
+ self.n_head = config.n_head
28
+ self.n_embd = config.n_embd
29
+ self.hyper_dim = 2**config.level
30
+
31
+ # Projections
32
+ self.q_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
33
+ self.k_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
34
+ self.v_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
35
+ self.out_proj = HyperLinear(self.n_embd, self.n_embd, level=self.level)
36
+
37
+ self.rope = GyRoPE(
38
+ self.head_size, config.block_size, self.n_head, level=self.level
39
+ )
40
+
41
+ self.register_buffer(
42
+ "tril", torch.tril(torch.ones(config.block_size, config.block_size))
43
+ )
44
+ self.tril: torch.Tensor
45
+
46
+ self.dropout = nn.Dropout(config.dropout)
47
+
48
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
49
+ # x Shape: [B, T, C, hyper_dim]
50
+ B, T, C, hyper_dim = x.shape
51
+
52
+ q = self.q_proj(x)
53
+ k = self.k_proj(x)
54
+ v = self.v_proj(x)
55
+
56
+ q = q.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
57
+ k = k.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
58
+ v = v.view(B, T, self.n_head, self.head_size, hyper_dim).transpose(1, 2)
59
+
60
+ q = self.rope(q)
61
+ k = self.rope(k)
62
+
63
+ # [B, nh, T, hs, hyper_dim] -> [B, nh, T, hs * hyper_dim]
64
+ q_flat = q.flatten(start_dim=-2)
65
+ k_flat = k.flatten(start_dim=-2)
66
+ v_flat = v.flatten(start_dim=-2)
67
+
68
+ wei = q_flat @ k_flat.adjoint() * (self.hyper_dim * self.head_size) ** -0.5
69
+ wei = wei.real
70
+ wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf"))
71
+ wei = F.softmax(wei, dim=-1)
72
+ wei = self.dropout(wei)
73
+
74
+ wei_complex = wei.to(torch.complex64)
75
+
76
+ out_flat = wei_complex @ v_flat # -> [B, nh, T, hs * hyper_dim]
77
+
78
+ # 6. Un-Flatten und Recombine: [B, nh, T, hs * hyper_dim] -> [B, nh, T, hs, hyper_dim]
79
+ out = out_flat.view(B, self.n_head, T, self.head_size, hyper_dim)
80
+
81
+ # -> [B, T, C, hyper_dim]
82
+ out = out.transpose(1, 2).contiguous().view(B, T, C, hyper_dim)
83
+
84
+ out = self.out_proj(out)
85
+
86
+ return out
87
+
88
+
89
+
90
+ class HyperBlock(nn.Module):
91
+ """Transformer block: Attention + SAIL with Residuals & RMSNorm"""
92
+
93
+ def __init__(self, config: ModelConfig):
94
+ super().__init__()
95
+ self.sa = HyperSelfAttention(config)
96
+ self.sail = SAIL(dim=config.n_embd, level=config.level)
97
+ self.ln1 = HyperRMSNorm(config.n_embd, level=config.level)
98
+ self.ln2 = HyperRMSNorm(config.n_embd, level=config.level)
99
+
100
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
101
+ # Attention + Residual
102
+ x = x + self.ln1(self.sa(x))
103
+ # FeedForward (SAIL) + Residual
104
+ x = x + self.ln2(self.sail(x))
105
+ return x
106
+
107
+
108
+ class HyperFormer(nn.Module, LMCoreMixin):
109
+ """
110
+ Universal Activation-Free Hypercomplex Transformer (v0.2.0).
111
+ Dynamically scaled via `config.level` (1=Complex, 2=Quaternion, 3=Octonion, 4=Sedenion...).
112
+ """
113
+
114
+ def __init__(self, config: ModelConfig):
115
+ super().__init__()
116
+ self.config = config
117
+ self.level = config.level
118
+
119
+ self.token_embedding = HyperEmbedding(
120
+ config.vocab_size, config.n_embd, level=self.level
121
+ )
122
+ self.blocks = nn.ModuleList([HyperBlock(config) for _ in range(config.n_layer)])
123
+ self.ln_f = HyperRMSNorm(config.n_embd, level=self.level)
124
+ self.lm_head = HyperLMHead(config.n_embd, config.vocab_size, level=self.level)
125
+
126
+ def forward(self, idx: torch.Tensor, targets: torch.Tensor | None = None):
127
+ B, T = idx.shape
128
+ x = self.token_embedding(idx)
129
+
130
+ for block in self.blocks:
131
+ x = block(x)
132
+
133
+ x = self.ln_f(x)
134
+ logits = self.lm_head(x)
135
+
136
+ if targets is None:
137
+ loss = None
138
+ else:
139
+ B, T, C = logits.shape
140
+ logits_flat = logits.view(B * T, C)
141
+ targets_flat = targets.view(B * T)
142
+ loss = F.cross_entropy(logits_flat, targets_flat)
143
+
144
+ return logits, loss
tardits/models/real.py ADDED
@@ -0,0 +1,167 @@
1
+ import torch
2
+ import torch.nn as nn
3
+ from torch.nn import functional as F
4
+
5
+ from tardits.modules import LMCoreMixin
6
+ from tardits.modules.conf import ModelConfig
7
+
8
+
9
+ class RotaryPositionalEmbedding(nn.Module):
10
+ """๐ŸŒ€ Rotates query/key pairwise in the complex plane based on position."""
11
+
12
+ def __init__(self, head_size, block_size):
13
+ super().__init__()
14
+ self.head_size = head_size
15
+
16
+ inv_freq = 1.0 / (
17
+ 10000.0 ** (torch.arange(0, head_size, 2).float() / head_size)
18
+ )
19
+ t = torch.arange(block_size, dtype=torch.float32)
20
+ freqs = torch.outer(t, inv_freq)
21
+ self.emb = torch.cat((freqs, freqs), dim=-1)
22
+
23
+ self.register_buffer("cos_cached", self.emb.cos(), persistent=False)
24
+ self.register_buffer("sin_cached", self.emb.sin(), persistent=False)
25
+ self.cos_cached: torch.Tensor
26
+ self.sin_cached: torch.Tensor
27
+
28
+ def _rotate_half(self, x):
29
+ x1 = x[..., : self.head_size // 2]
30
+ x2 = x[..., self.head_size // 2 :]
31
+ return torch.cat((-x2, x1), dim=-1)
32
+
33
+ def forward(self, x):
34
+ # x.shape: (B, nh, T, hs)
35
+ T = x.shape[2]
36
+ cos = self.cos_cached[:T, :].unsqueeze(0).unsqueeze(1) # (1, 1, T, hs)
37
+ sin = self.sin_cached[:T, :].unsqueeze(0).unsqueeze(1) # (1, 1, T, hs)
38
+
39
+ return (x * cos) + (self._rotate_half(x) * sin)
40
+
41
+
42
+ RoPE = RotaryPositionalEmbedding
43
+
44
+
45
+ class SwiGLU(nn.Module):
46
+ """๐Ÿš€ SwiGLU Feed-Forward Network."""
47
+
48
+ def __init__(self, n_embd, hidden_dim, dropout):
49
+ super().__init__()
50
+ self.w = nn.Linear(n_embd, hidden_dim, bias=False)
51
+ self.v = nn.Linear(n_embd, hidden_dim, bias=False)
52
+ self.out_proj = nn.Linear(hidden_dim, n_embd, bias=False)
53
+ self.dropout = nn.Dropout(dropout)
54
+
55
+ def forward(self, x):
56
+ gate = F.silu(self.w(x))
57
+ activated = gate * self.v(x)
58
+ out = self.out_proj(activated)
59
+
60
+ return self.dropout(out)
61
+
62
+
63
+ class CasualSelfAttention(nn.Module):
64
+ """casual (and causal) multi-headed self-attention"""
65
+
66
+ def __init__(self, config):
67
+ super().__init__()
68
+ self.config = config
69
+ self.head_size = config.n_embd // config.n_head
70
+ self.n_head = config.n_head
71
+ self.n_embd = config.n_embd
72
+
73
+ self.c_attn = nn.Linear(self.n_embd, 3 * self.n_embd, bias=False)
74
+
75
+ self.rope = RoPE(self.head_size, config.block_size)
76
+
77
+ self.register_buffer(
78
+ "tril", torch.tril(torch.ones(config.block_size, config.block_size))
79
+ )
80
+ self.tril: torch.Tensor
81
+
82
+ self.dropout = nn.Dropout(config.dropout)
83
+
84
+ self.proj = nn.Linear(config.n_embd, config.n_embd)
85
+ self.proj_dropout = nn.Dropout(config.dropout)
86
+
87
+ def forward(self, x):
88
+ B, T, C = x.shape
89
+
90
+ qkv = self.c_attn(x) # (B,T,3*C)
91
+ q, k, v = qkv.split(C, dim=2) # (B,T,C)
92
+
93
+ # view(B,T,nh,hs): (B,T,C) == (B,T,nh*hs) --> (B,T,nh,hs)
94
+ # transpose(1, 2): (B,T,nh,hs) --> (B,nh,T,hs)
95
+ q = q.view(B, T, self.n_head, self.head_size).transpose(1, 2)
96
+ k = k.view(B, T, self.n_head, self.head_size).transpose(1, 2)
97
+ v = v.view(B, T, self.n_head, self.head_size).transpose(1, 2)
98
+
99
+ # fancy RotaryPositionalEmbedding
100
+ q = self.rope(q)
101
+ k = self.rope(k)
102
+
103
+ wei = q @ k.transpose(-2, -1) * (self.head_size**-0.5) # (B,nh,T,T)
104
+ wei = wei.masked_fill(self.tril[:T, :T] == 0, float("-inf")) # (B,nh,T,T)
105
+ wei = F.softmax(wei, dim=-1) # (B,nh,T,T)
106
+ wei = self.dropout(wei) # (B,nh,T,T)
107
+
108
+ # weighted aggregation of value
109
+ out = wei @ v # (B,nh,T,T) @ (B,nh,T,hs) --> (B,nh,T,hs)
110
+
111
+ out = out.transpose(1, 2).reshape(B, T, C) # -> (B,T,nh,hs) -> (B,T,C)
112
+
113
+ out = self.proj(out)
114
+ out = self.proj_dropout(out)
115
+
116
+ return out # (B,T,C)
117
+
118
+
119
+ class Block(nn.Module):
120
+ """Transformer block: communication followed by computation"""
121
+
122
+ def __init__(self, config):
123
+ # n_embd: embedding dimension, n_head: number of heads
124
+ super().__init__()
125
+ self.sa = CasualSelfAttention(config)
126
+ self.ffwd = SwiGLU(config.n_embd, int(config.n_embd * (8 / 3)), config.dropout)
127
+ self.ln1 = nn.RMSNorm(config.n_embd)
128
+ self.ln2 = nn.RMSNorm(config.n_embd)
129
+
130
+ def forward(self, x):
131
+ x = x + self.ln1(self.sa(x)) # x + because "residual connections"...
132
+ x = x + self.ln2(self.ffwd(x))
133
+ return x
134
+
135
+
136
+ class RealGPT(nn.Module, LMCoreMixin):
137
+ def __init__(
138
+ self,
139
+ config: ModelConfig,
140
+ ):
141
+ super().__init__()
142
+
143
+ self.config = config
144
+ self.token_embedding_table = nn.Embedding(config.vocab_size, config.n_embd)
145
+
146
+ self.blocks = nn.Sequential(*[Block(config) for _ in range(config.n_layer)])
147
+
148
+ self.ln_f = nn.RMSNorm(config.n_embd)
149
+ self.lm_head = nn.Linear(config.n_embd, config.vocab_size)
150
+
151
+ def forward(self, idx, targets=None):
152
+ B, T = idx.shape
153
+ x = self.token_embedding_table(idx) # (B,T,C)
154
+ x = self.blocks(x)
155
+ x = self.ln_f(x)
156
+ logits = self.lm_head(x)
157
+
158
+ if targets is None:
159
+ loss = None
160
+ else:
161
+ B, T, C = logits.shape
162
+ logits = logits.view(B * T, C)
163
+ targets = targets.view(B * T)
164
+
165
+ loss = F.cross_entropy(logits, targets)
166
+
167
+ return logits, loss
@@ -0,0 +1,27 @@
1
+ from .trainer import Trainer
2
+ from .base import get_device
3
+ from .conf import ModelConfig, TrainerConfig, MLFlowConfig
4
+ from .core import (
5
+ SAIL,
6
+ GyRoPE,
7
+ HyperRMSNorm,
8
+ HyperEmbedding,
9
+ HyperLMHead,
10
+ HyperLinear,
11
+ )
12
+ from .base import LMCoreMixin
13
+
14
+ __all__ = [
15
+ "Trainer",
16
+ "ModelConfig",
17
+ "TrainerConfig",
18
+ "MLFlowConfig",
19
+ "SAIL",
20
+ "GyRoPE",
21
+ "LMCoreMixin"
22
+ "HyperRMSNorm",
23
+ "HyperEmbedding",
24
+ "HyperLMHead",
25
+ "HyperLinear",
26
+ "get_device"
27
+ ]
@@ -0,0 +1,99 @@
1
+ # pyright: reportAttributeAccessIssue=false
2
+ import torch
3
+ from torch.nn import functional as F
4
+ import os
5
+
6
+
7
+ def get_device(requested_device: str = "auto") -> torch.device:
8
+ if requested_device == "auto" or not requested_device:
9
+ if torch.cuda.is_available():
10
+ return torch.device("cuda")
11
+ elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
12
+ return torch.device("mps") # Apple Silicon GPU
13
+ return torch.device("cpu")
14
+ return torch.device(requested_device)
15
+
16
+
17
+ class LMCoreMixin:
18
+ @torch.no_grad()
19
+ def generate(self, idx, max_new_tokens, temp=1.0, top_k=10):
20
+ device = next(self.parameters()).device
21
+ idx = idx.to(device)
22
+ # idx is (B,T) array of indices in the current context
23
+ for _ in range(max_new_tokens):
24
+ # crop idx to the last block_size max_new_tokens
25
+ idx_cond = idx[:, -self.config.block_size :]
26
+ # get predictions
27
+ logits, _ = self.forward(idx_cond)
28
+ # focus only on the last timestep
29
+ logits = logits[:, -1, :] # becomes (B,C)
30
+ # temp
31
+ logits = logits / temp
32
+ # top_k
33
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
34
+ logits[logits < v[:, [-1]]] = -float("Inf")
35
+ # apply softmax to get probabilities
36
+ probs = F.softmax(logits, dim=-1) # (B,C)
37
+ # sample from distribution
38
+ idx_next = torch.multinomial(probs, num_samples=1) # (B,1)
39
+ # append sampled index to the running sequence
40
+ idx = torch.cat((idx, idx_next), dim=1) # (B,T+1)
41
+ return idx
42
+
43
+ @torch.no_grad()
44
+ def generate_stream(self, idx, temp=1.0, top_k=10):
45
+ while True:
46
+ # slicing:
47
+ idx_cond = idx[:, -self.config.block_size :]
48
+ logits, _ = self.forward(idx_cond)
49
+ logits = logits[:, -1, :] # becomes (B,C)
50
+
51
+ # temp/top_k
52
+ logits = logits / temp
53
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
54
+ logits[logits < v[:, [-1]]] = -float("Inf")
55
+
56
+ # sampling
57
+ probs = F.softmax(logits, dim=-1) # (B,C)
58
+ idx_next = torch.multinomial(probs, num_samples=1) # (B,1)
59
+
60
+ # append
61
+ idx = torch.cat((idx, idx_next), dim=1) # (B,T+1)
62
+
63
+ # yield single token
64
+ yield idx_next.item()
65
+
66
+ def save_checkpoint(self, filepath="checkpoint.pt"):
67
+ """saves model weights and config to disk"""
68
+ dirname = os.path.dirname(filepath)
69
+ if dirname:
70
+ os.makedirs(dirname, exist_ok=True)
71
+ checkpoint = {"model_state_dict": self.state_dict(), "config": self.config}
72
+ torch.save(checkpoint, filepath)
73
+ # print(f"๐Ÿ’พ checkpoint successfully saved: {filepath}")
74
+
75
+ def get_num_params(self):
76
+ """returns number of trainable weights"""
77
+ total_params = 0
78
+ for p in self.parameters():
79
+ if p.requires_grad:
80
+ if p.is_complex():
81
+ total_params += p.numel() * 2
82
+ else:
83
+ total_params += p.numel()
84
+ return total_params
85
+
86
+ @classmethod
87
+ def load_from_checkpoint(
88
+ cls, filepath="checkpoint.pt", device: str | torch.device = "cpu"
89
+ ):
90
+ """loads model state from checkpoint file"""
91
+ if not os.path.exists(filepath):
92
+ raise FileNotFoundError(f"โŒ file not found:\n{filepath}")
93
+
94
+ checkpoint = torch.load(filepath, map_location=device, weights_only=False)
95
+ model = cls(checkpoint["config"]) # type: ignore
96
+ model.load_state_dict(checkpoint["model_state_dict"])
97
+ model.to(device)
98
+ # print(f"๐Ÿš€ checkpoint successfully restored from:\n'{filepath}'")
99
+ return model
@@ -0,0 +1,31 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class ModelConfig:
6
+ level: int = 1
7
+ block_size: int = 8 # maximum context length
8
+ vocab_size: int = 74
9
+ n_head: int = 4
10
+ n_embd: int = 32
11
+ n_layer: int = 3
12
+ dropout: float = 0.2
13
+
14
+
15
+ @dataclass
16
+ class TrainerConfig:
17
+ batch_size: int = 32 # how many independent sequences processed in parallel?
18
+ max_iters: int = 5000
19
+ eval_interval: int = 500
20
+ learning_rate: float = 3e-4
21
+ eval_iters: int = 200
22
+ checkpoint: str = ""
23
+ save_interval: int = 0
24
+
25
+
26
+ @dataclass
27
+ class MLFlowConfig:
28
+ experiment: str = "sail_experiment"
29
+ log_interval: int = 10
30
+ deep_log_interval: int = 100
31
+ system_metrics: bool = False