matt1847 Claude Opus 4.5 commited on
Commit
f94169f
·
1 Parent(s): d1033d4

機能追加: モデルラインナップ拡張とGradio UI移行

Browse files

- 新モデル11種追加(GPT-OSS, Pythia, OLMo, BLOOM, Llama, Qwen, Mistral)
- Gradio UIへ移行(ZeroGPU対応で無料GPU利用可能)
- Streamlit UIをバックアップとして src/ui/streamlit/ に保持
- 全62テストパス

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

app.py CHANGED
@@ -1,32 +1,19 @@
1
  """
2
- WILL - Pure Computational Will
3
 
4
  言語モデルにランダムノイズを入力し、
5
  人間の問いかけなしにモデルの構造だけが
6
  出力するものを観測する
7
- """
8
- import streamlit as st
9
 
10
- from src.ui.styles import CUSTOM_CSS
11
- from src.ui.pages import render_generate_page, render_concept_page
 
12
 
13
 
14
  def main():
15
  """アプリケーションのエントリーポイント"""
16
- # ページ設定
17
- st.set_page_config(page_title="will", page_icon="", layout="centered")
18
-
19
- # カスタムCSS適用
20
- st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
21
-
22
- # タブ構成
23
- tab1, tab2 = st.tabs(["GENERATE", "CONCEPT"])
24
-
25
- with tab1:
26
- render_generate_page()
27
-
28
- with tab2:
29
- render_concept_page()
30
 
31
 
32
  if __name__ == "__main__":
 
1
  """
2
+ WILL - Pure Computational Will (Gradio版)
3
 
4
  言語モデルにランダムノイズを入力し、
5
  人間の問いかけなしにモデルの構造だけが
6
  出力するものを観測する
 
 
7
 
8
+ ZeroGPU対応 - Hugging Face Spacesで無料GPU利用可能
9
+ """
10
+ from src.ui.gradio import create_app
11
 
12
 
13
  def main():
14
  """アプリケーションのエントリーポイント"""
15
+ app = create_app()
16
+ app.launch()
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
 
19
  if __name__ == "__main__":
app_streamlit.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WILL - Pure Computational Will (Streamlit版)
3
+
4
+ 言語モデルにランダムノイズを入力し、
5
+ 人間の問いかけなしにモデルの構造だけが
6
+ 出力するものを観測する
7
+ """
8
+ import streamlit as st
9
+
10
+ from src.ui.streamlit.styles import CUSTOM_CSS
11
+ from src.ui.streamlit.pages import render_generate_page, render_concept_page
12
+
13
+
14
+ def main():
15
+ """アプリケーションのエントリーポイント"""
16
+ # ページ設定
17
+ st.set_page_config(page_title="will", page_icon="", layout="centered")
18
+
19
+ # カスタムCSS適用
20
+ st.markdown(CUSTOM_CSS, unsafe_allow_html=True)
21
+
22
+ # タブ構成
23
+ tab1, tab2 = st.tabs(["GENERATE", "CONCEPT"])
24
+
25
+ with tab1:
26
+ render_generate_page()
27
+
28
+ with tab2:
29
+ render_concept_page()
30
+
31
+
32
+ if __name__ == "__main__":
33
+ main()
pytest.ini ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ [pytest]
2
+ markers =
3
+ slow: marks tests as slow (deselect with '-m "not slow"')
src/models/bloom.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ BLOOM モデル実装
3
+
4
+ BigScienceによる完全オープンソースモデル
5
+ 多言語対応、ALiBi位置埋め込みを採用
6
+ """
7
+ from typing import List, Tuple
8
+
9
+ import torch
10
+ from transformers import BloomForCausalLM, AutoTokenizer
11
+
12
+ from .base import BaseLanguageModel, ModelConfig
13
+
14
+
15
+ # BLOOM 560M設定
16
+ BLOOM_560M_CONFIG = ModelConfig(
17
+ name="BLOOM 560M",
18
+ model_id="bigscience/bloom-560m",
19
+ embedding_dim=1024,
20
+ vocab_size=250880,
21
+ )
22
+
23
+
24
+ class BLOOMModel(BaseLanguageModel):
25
+ """
26
+ BLOOMモデルの実装
27
+
28
+ BigScienceが公開した完全オープンソースモデル。
29
+ 多言語対応、ALiBi位置埋め込みを採用。
30
+ """
31
+
32
+ # 出力ノイズの倍率(学習バイアス破壊用)
33
+ LOGITS_NOISE_SCALE = 10.0
34
+
35
+ def load(self) -> None:
36
+ """モデルとトークナイザーをロード"""
37
+ if self._is_loaded:
38
+ return
39
+
40
+ try:
41
+ self._model = BloomForCausalLM.from_pretrained(self._config.model_id)
42
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
43
+ self._model.eval()
44
+ self._is_loaded = True
45
+ except Exception as e:
46
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
47
+
48
+ def forward_with_noise(
49
+ self, noise: torch.Tensor
50
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
51
+ """
52
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
53
+
54
+ Args:
55
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
56
+
57
+ Returns:
58
+ Tuple[logits, corrupted_logits]
59
+ """
60
+ if not self._is_loaded:
61
+ raise RuntimeError("Model not loaded. Call load() first.")
62
+
63
+ with torch.no_grad():
64
+ outputs = self._model(inputs_embeds=noise)
65
+ logits = outputs.logits
66
+
67
+ # 出力logitsにノイズを加算して学習バイアスを破壊
68
+ logits_noise = (
69
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
70
+ )
71
+ corrupted_logits = logits + logits_noise
72
+
73
+ return logits, corrupted_logits
74
+
75
+ def decode_indices(self, indices: List[int]) -> List[str]:
76
+ """トークンインデックスをデコード"""
77
+ if not self._is_loaded:
78
+ raise RuntimeError("Model not loaded. Call load() first.")
79
+
80
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/gpt_oss.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ GPT-OSS モデル実装
3
+
4
+ OpenAIの完全オープンソースモデル(Apache 2.0)
5
+ MoEアーキテクチャで21Bパラメータ、active 3.6B
6
+ """
7
+ from typing import List, Tuple
8
+
9
+ import torch
10
+ from transformers import AutoModelForCausalLM, AutoTokenizer
11
+
12
+ from .base import BaseLanguageModel, ModelConfig
13
+
14
+
15
+ # GPT-OSS 20B設定(MoEモデル)
16
+ GPT_OSS_20B_CONFIG = ModelConfig(
17
+ name="GPT-OSS 20B (MoE)",
18
+ model_id="openai/gpt-oss-20b",
19
+ embedding_dim=4096,
20
+ vocab_size=128000,
21
+ )
22
+
23
+
24
+ class GPTOSSModel(BaseLanguageModel):
25
+ """
26
+ GPT-OSS MoEモデルの実装
27
+
28
+ OpenAIがApache 2.0でリリースした完全オープンソースモデル。
29
+ 21Bパラメータ(active 3.6B)のMoEアーキテクチャ。
30
+ """
31
+
32
+ # 出力ノイズの倍率(学習バイアス破壊用)
33
+ LOGITS_NOISE_SCALE = 10.0
34
+
35
+ def load(self) -> None:
36
+ """モデルとトークナイザーをロード"""
37
+ if self._is_loaded:
38
+ return
39
+
40
+ try:
41
+ self._model = AutoModelForCausalLM.from_pretrained(
42
+ self._config.model_id,
43
+ torch_dtype="auto",
44
+ device_map="auto",
45
+ )
46
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
47
+ self._model.eval()
48
+ self._is_loaded = True
49
+ except Exception as e:
50
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
51
+
52
+ def forward_with_noise(
53
+ self, noise: torch.Tensor
54
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
55
+ """
56
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
57
+
58
+ Args:
59
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
60
+
61
+ Returns:
62
+ Tuple[logits, corrupted_logits]
63
+ """
64
+ if not self._is_loaded:
65
+ raise RuntimeError("Model not loaded. Call load() first.")
66
+
67
+ with torch.no_grad():
68
+ outputs = self._model(inputs_embeds=noise)
69
+ logits = outputs.logits
70
+
71
+ # 出力logitsにノイズを加算して学習バイアスを破壊
72
+ logits_noise = (
73
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
74
+ )
75
+ corrupted_logits = logits + logits_noise
76
+
77
+ return logits, corrupted_logits
78
+
79
+ def decode_indices(self, indices: List[int]) -> List[str]:
80
+ """トークンインデックスをデコード"""
81
+ if not self._is_loaded:
82
+ raise RuntimeError("Model not loaded. Call load() first.")
83
+
84
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/llama.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Llama モデル実装
3
+
4
+ Meta社の最新Llamaモデル
5
+ GQA/RoPE/SwiGLUなど最新アーキテクチャを採用
6
+ HuggingFace認証が必要
7
+ """
8
+ from typing import List, Tuple
9
+
10
+ import torch
11
+ from transformers import AutoModelForCausalLM, AutoTokenizer
12
+
13
+ from .base import BaseLanguageModel, ModelConfig
14
+
15
+
16
+ # Llama 3.2 1B設定
17
+ LLAMA_3_2_1B_CONFIG = ModelConfig(
18
+ name="Llama 3.2 1B",
19
+ model_id="meta-llama/Llama-3.2-1B",
20
+ embedding_dim=2048,
21
+ vocab_size=128256,
22
+ )
23
+
24
+ # Llama 3.2 3B設定
25
+ LLAMA_3_2_3B_CONFIG = ModelConfig(
26
+ name="Llama 3.2 3B",
27
+ model_id="meta-llama/Llama-3.2-3B",
28
+ embedding_dim=3072,
29
+ vocab_size=128256,
30
+ )
31
+
32
+
33
+ class LlamaModel(BaseLanguageModel):
34
+ """
35
+ Llamaモデルの実装
36
+
37
+ Meta社の最新Llama 3.2シリーズ。
38
+ GQA/RoPE/SwiGLU採用。HuggingFace認証が必要。
39
+ """
40
+
41
+ # 出力ノイズの倍率(学習バイアス破壊用)
42
+ LOGITS_NOISE_SCALE = 10.0
43
+
44
+ def load(self) -> None:
45
+ """モデルとトークナイザーをロード"""
46
+ if self._is_loaded:
47
+ return
48
+
49
+ try:
50
+ self._model = AutoModelForCausalLM.from_pretrained(
51
+ self._config.model_id,
52
+ torch_dtype="auto",
53
+ )
54
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
55
+ self._model.eval()
56
+ self._is_loaded = True
57
+ except Exception as e:
58
+ raise RuntimeError(
59
+ f"Failed to load model {self._config.model_id}: {e}. "
60
+ "Note: Llama models require HuggingFace authentication. "
61
+ "Run 'huggingface-cli login' first."
62
+ )
63
+
64
+ def forward_with_noise(
65
+ self, noise: torch.Tensor
66
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
67
+ """
68
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
69
+
70
+ Args:
71
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
72
+
73
+ Returns:
74
+ Tuple[logits, corrupted_logits]
75
+ """
76
+ if not self._is_loaded:
77
+ raise RuntimeError("Model not loaded. Call load() first.")
78
+
79
+ with torch.no_grad():
80
+ outputs = self._model(inputs_embeds=noise)
81
+ logits = outputs.logits
82
+
83
+ # 出力logitsにノイズを加算して学習バイアスを破壊
84
+ logits_noise = (
85
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
86
+ )
87
+ corrupted_logits = logits + logits_noise
88
+
89
+ return logits, corrupted_logits
90
+
91
+ def decode_indices(self, indices: List[int]) -> List[str]:
92
+ """トークンインデックスをデコード"""
93
+ if not self._is_loaded:
94
+ raise RuntimeError("Model not loaded. Call load() first.")
95
+
96
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/mistral.py ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Mistral モデル実装
3
+
4
+ Mistral AI社のモデル
5
+ Sliding Window Attention、GQA採用
6
+ """
7
+ from typing import List, Tuple
8
+
9
+ import torch
10
+ from transformers import AutoModelForCausalLM, AutoTokenizer
11
+
12
+ from .base import BaseLanguageModel, ModelConfig
13
+
14
+
15
+ # Mistral 7B設定
16
+ MISTRAL_7B_CONFIG = ModelConfig(
17
+ name="Mistral 7B v0.3",
18
+ model_id="mistralai/Mistral-7B-v0.3",
19
+ embedding_dim=4096,
20
+ vocab_size=32768,
21
+ )
22
+
23
+
24
+ class MistralModel(BaseLanguageModel):
25
+ """
26
+ Mistralモデルの実装
27
+
28
+ Mistral AI社のMistral 7Bシリーズ。
29
+ Sliding Window Attention、GQA採用。
30
+ """
31
+
32
+ # 出力ノイズの倍率(学習バイアス破壊用)
33
+ LOGITS_NOISE_SCALE = 10.0
34
+
35
+ def load(self) -> None:
36
+ """モデルとトークナイザーをロード"""
37
+ if self._is_loaded:
38
+ return
39
+
40
+ try:
41
+ self._model = AutoModelForCausalLM.from_pretrained(
42
+ self._config.model_id,
43
+ torch_dtype="auto",
44
+ )
45
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
46
+ self._model.eval()
47
+ self._is_loaded = True
48
+ except Exception as e:
49
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
50
+
51
+ def forward_with_noise(
52
+ self, noise: torch.Tensor
53
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
54
+ """
55
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
56
+
57
+ Args:
58
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
59
+
60
+ Returns:
61
+ Tuple[logits, corrupted_logits]
62
+ """
63
+ if not self._is_loaded:
64
+ raise RuntimeError("Model not loaded. Call load() first.")
65
+
66
+ with torch.no_grad():
67
+ outputs = self._model(inputs_embeds=noise)
68
+ logits = outputs.logits
69
+
70
+ # 出力logitsにノイズを加算して学習バイアスを破壊
71
+ logits_noise = (
72
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
73
+ )
74
+ corrupted_logits = logits + logits_noise
75
+
76
+ return logits, corrupted_logits
77
+
78
+ def decode_indices(self, indices: List[int]) -> List[str]:
79
+ """トークンインデックスをデコード"""
80
+ if not self._is_loaded:
81
+ raise RuntimeError("Model not loaded. Call load() first.")
82
+
83
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/olmo.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ OLMo モデル実装
3
+
4
+ Allen AIによる完全オープンソースモデル
5
+ 学習データ(Dolma)とアーキテクチャが完全公開
6
+ SwiGLU/RoPEなど最新アーキテクチャを採用
7
+ """
8
+ from typing import List, Tuple
9
+
10
+ import torch
11
+ from transformers import AutoModelForCausalLM, AutoTokenizer
12
+
13
+ from .base import BaseLanguageModel, ModelConfig
14
+
15
+
16
+ # OLMo 1B設定
17
+ OLMO_1B_CONFIG = ModelConfig(
18
+ name="OLMo 1B",
19
+ model_id="allenai/OLMo-1B-hf",
20
+ embedding_dim=2048,
21
+ vocab_size=50304,
22
+ )
23
+
24
+ # OLMo 7B設定
25
+ OLMO_7B_CONFIG = ModelConfig(
26
+ name="OLMo 7B",
27
+ model_id="allenai/OLMo-7B-hf",
28
+ embedding_dim=4096,
29
+ vocab_size=50304,
30
+ )
31
+
32
+
33
+ class OLMoModel(BaseLanguageModel):
34
+ """
35
+ OLMoモデルの実装
36
+
37
+ Allen AIが公開した完全オープンソースモデル。
38
+ 学習データ(Dolma)も公開。SwiGLU/RoPE採用。
39
+ """
40
+
41
+ # 出力ノイズの倍率(学習バイアス破壊用)
42
+ LOGITS_NOISE_SCALE = 10.0
43
+
44
+ def load(self) -> None:
45
+ """モデルとトークナイザーをロード"""
46
+ if self._is_loaded:
47
+ return
48
+
49
+ try:
50
+ self._model = AutoModelForCausalLM.from_pretrained(
51
+ self._config.model_id,
52
+ trust_remote_code=True,
53
+ )
54
+ self._tokenizer = AutoTokenizer.from_pretrained(
55
+ self._config.model_id,
56
+ trust_remote_code=True,
57
+ )
58
+ self._model.eval()
59
+ self._is_loaded = True
60
+ except Exception as e:
61
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
62
+
63
+ def forward_with_noise(
64
+ self, noise: torch.Tensor
65
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
66
+ """
67
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
68
+
69
+ Args:
70
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
71
+
72
+ Returns:
73
+ Tuple[logits, corrupted_logits]
74
+ """
75
+ if not self._is_loaded:
76
+ raise RuntimeError("Model not loaded. Call load() first.")
77
+
78
+ with torch.no_grad():
79
+ outputs = self._model(inputs_embeds=noise)
80
+ logits = outputs.logits
81
+
82
+ # 出力logitsにノイズを加算して学習バイアスを破壊
83
+ logits_noise = (
84
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
85
+ )
86
+ corrupted_logits = logits + logits_noise
87
+
88
+ return logits, corrupted_logits
89
+
90
+ def decode_indices(self, indices: List[int]) -> List[str]:
91
+ """トークンインデックスをデコード"""
92
+ if not self._is_loaded:
93
+ raise RuntimeError("Model not loaded. Call load() first.")
94
+
95
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/pythia.py ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Pythia モデル実装
3
+
4
+ EleutherAIによる完全オープンソースモデル
5
+ 学習データ(The Pile)とアーキテクチャが完全公開
6
+ """
7
+ from typing import List, Tuple
8
+
9
+ import torch
10
+ from transformers import GPTNeoXForCausalLM, AutoTokenizer
11
+
12
+ from .base import BaseLanguageModel, ModelConfig
13
+
14
+
15
+ # Pythia 410M設定
16
+ PYTHIA_410M_CONFIG = ModelConfig(
17
+ name="Pythia 410M",
18
+ model_id="EleutherAI/pythia-410m",
19
+ embedding_dim=1024,
20
+ vocab_size=50304,
21
+ )
22
+
23
+ # Pythia 1B設定
24
+ PYTHIA_1B_CONFIG = ModelConfig(
25
+ name="Pythia 1B",
26
+ model_id="EleutherAI/pythia-1b",
27
+ embedding_dim=2048,
28
+ vocab_size=50304,
29
+ )
30
+
31
+
32
+ class PythiaModel(BaseLanguageModel):
33
+ """
34
+ Pythiaモデルの実装(GPT-NeoXベース)
35
+
36
+ EleutherAIが公開した完全オープンソースモデル。
37
+ 学習データ(The Pile)も公開されている。
38
+ """
39
+
40
+ # 出力ノイズの倍率(学習バイアス破壊用)
41
+ LOGITS_NOISE_SCALE = 10.0
42
+
43
+ def load(self) -> None:
44
+ """モデルとトークナイザーをロード"""
45
+ if self._is_loaded:
46
+ return
47
+
48
+ try:
49
+ self._model = GPTNeoXForCausalLM.from_pretrained(self._config.model_id)
50
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
51
+ self._model.eval()
52
+ self._is_loaded = True
53
+ except Exception as e:
54
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
55
+
56
+ def forward_with_noise(
57
+ self, noise: torch.Tensor
58
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
59
+ """
60
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
61
+
62
+ Args:
63
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
64
+
65
+ Returns:
66
+ Tuple[logits, corrupted_logits]
67
+ """
68
+ if not self._is_loaded:
69
+ raise RuntimeError("Model not loaded. Call load() first.")
70
+
71
+ with torch.no_grad():
72
+ outputs = self._model(inputs_embeds=noise)
73
+ logits = outputs.logits
74
+
75
+ # 出力logitsにノイズを加算して学習バイアスを破壊
76
+ logits_noise = (
77
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
78
+ )
79
+ corrupted_logits = logits + logits_noise
80
+
81
+ return logits, corrupted_logits
82
+
83
+ def decode_indices(self, indices: List[int]) -> List[str]:
84
+ """トークンインデックスをデコード"""
85
+ if not self._is_loaded:
86
+ raise RuntimeError("Model not loaded. Call load() first.")
87
+
88
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/qwen.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Qwen モデル実装
3
+
4
+ Alibaba社のQwen2.5シリーズ
5
+ Apache 2.0ライセンス、最新アーキテクチャ採用
6
+ """
7
+ from typing import List, Tuple
8
+
9
+ import torch
10
+ from transformers import AutoModelForCausalLM, AutoTokenizer
11
+
12
+ from .base import BaseLanguageModel, ModelConfig
13
+
14
+
15
+ # Qwen2.5 0.5B設定
16
+ QWEN_2_5_0_5B_CONFIG = ModelConfig(
17
+ name="Qwen2.5 0.5B",
18
+ model_id="Qwen/Qwen2.5-0.5B",
19
+ embedding_dim=896,
20
+ vocab_size=151936,
21
+ )
22
+
23
+ # Qwen2.5 1.5B設定
24
+ QWEN_2_5_1_5B_CONFIG = ModelConfig(
25
+ name="Qwen2.5 1.5B",
26
+ model_id="Qwen/Qwen2.5-1.5B",
27
+ embedding_dim=1536,
28
+ vocab_size=151936,
29
+ )
30
+
31
+
32
+ class QwenModel(BaseLanguageModel):
33
+ """
34
+ Qwenモデルの実装
35
+
36
+ Alibaba社のQwen2.5シリーズ。
37
+ Apache 2.0ライセンス、最新アーキテクチャ採用。
38
+ """
39
+
40
+ # 出力ノイズの倍率(学習バイアス破壊用)
41
+ LOGITS_NOISE_SCALE = 10.0
42
+
43
+ def load(self) -> None:
44
+ """モデルとトークナイザーをロード"""
45
+ if self._is_loaded:
46
+ return
47
+
48
+ try:
49
+ self._model = AutoModelForCausalLM.from_pretrained(
50
+ self._config.model_id,
51
+ torch_dtype="auto",
52
+ )
53
+ self._tokenizer = AutoTokenizer.from_pretrained(self._config.model_id)
54
+ self._model.eval()
55
+ self._is_loaded = True
56
+ except Exception as e:
57
+ raise RuntimeError(f"Failed to load model {self._config.model_id}: {e}")
58
+
59
+ def forward_with_noise(
60
+ self, noise: torch.Tensor
61
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
62
+ """
63
+ ノイズを入力として順伝播を実行し、出力にもノイズを加算
64
+
65
+ Args:
66
+ noise: 入力ノイズテンソル [batch, seq_len, embedding_dim]
67
+
68
+ Returns:
69
+ Tuple[logits, corrupted_logits]
70
+ """
71
+ if not self._is_loaded:
72
+ raise RuntimeError("Model not loaded. Call load() first.")
73
+
74
+ with torch.no_grad():
75
+ outputs = self._model(inputs_embeds=noise)
76
+ logits = outputs.logits
77
+
78
+ # 出力logitsにノイズを加算して学習バイアスを破壊
79
+ logits_noise = (
80
+ torch.randn_like(logits) * logits.std() * self.LOGITS_NOISE_SCALE
81
+ )
82
+ corrupted_logits = logits + logits_noise
83
+
84
+ return logits, corrupted_logits
85
+
86
+ def decode_indices(self, indices: List[int]) -> List[str]:
87
+ """トークンインデックスをデコード"""
88
+ if not self._is_loaded:
89
+ raise RuntimeError("Model not loaded. Call load() first.")
90
+
91
+ return [self._tokenizer.decode([i]) for i in indices]
src/models/registry.py CHANGED
@@ -11,6 +11,17 @@ from .gpt2 import GPT2Model, GPT2_SMALL_CONFIG, GPT2_MEDIUM_CONFIG
11
  from .gpt_neo import GPTNeoModel, GPT_NEO_125M_CONFIG
12
  from .opt import OPTModel, OPT_125M_CONFIG
13
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  class ModelRegistry:
16
  """
@@ -84,5 +95,20 @@ ModelRegistry.register("gpt2-medium", GPT2Model, GPT2_MEDIUM_CONFIG)
84
  ModelRegistry.register("gpt-neo-125m", GPTNeoModel, GPT_NEO_125M_CONFIG)
85
  ModelRegistry.register("opt-125m", OPTModel, OPT_125M_CONFIG)
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  # デフォルトモデルキー
88
  DEFAULT_MODEL_KEY = "gpt2"
 
11
  from .gpt_neo import GPTNeoModel, GPT_NEO_125M_CONFIG
12
  from .opt import OPTModel, OPT_125M_CONFIG
13
 
14
+ # Phase 1: GPT-OSS and Fully Open Source Models
15
+ from .gpt_oss import GPTOSSModel, GPT_OSS_20B_CONFIG
16
+ from .pythia import PythiaModel, PYTHIA_410M_CONFIG, PYTHIA_1B_CONFIG
17
+ from .olmo import OLMoModel, OLMO_1B_CONFIG, OLMO_7B_CONFIG
18
+ from .bloom import BLOOMModel, BLOOM_560M_CONFIG
19
+
20
+ # Phase 2: Latest Architecture Models
21
+ from .llama import LlamaModel, LLAMA_3_2_1B_CONFIG, LLAMA_3_2_3B_CONFIG
22
+ from .qwen import QwenModel, QWEN_2_5_0_5B_CONFIG, QWEN_2_5_1_5B_CONFIG
23
+ from .mistral import MistralModel, MISTRAL_7B_CONFIG
24
+
25
 
26
  class ModelRegistry:
27
  """
 
95
  ModelRegistry.register("gpt-neo-125m", GPTNeoModel, GPT_NEO_125M_CONFIG)
96
  ModelRegistry.register("opt-125m", OPTModel, OPT_125M_CONFIG)
97
 
98
+ # Phase 1: GPT-OSS and Fully Open Source Models
99
+ ModelRegistry.register("gpt-oss-20b", GPTOSSModel, GPT_OSS_20B_CONFIG)
100
+ ModelRegistry.register("pythia-410m", PythiaModel, PYTHIA_410M_CONFIG)
101
+ ModelRegistry.register("pythia-1b", PythiaModel, PYTHIA_1B_CONFIG)
102
+ ModelRegistry.register("olmo-1b", OLMoModel, OLMO_1B_CONFIG)
103
+ ModelRegistry.register("olmo-7b", OLMoModel, OLMO_7B_CONFIG)
104
+ ModelRegistry.register("bloom-560m", BLOOMModel, BLOOM_560M_CONFIG)
105
+
106
+ # Phase 2: Latest Architecture Models
107
+ ModelRegistry.register("llama-3.2-1b", LlamaModel, LLAMA_3_2_1B_CONFIG)
108
+ ModelRegistry.register("llama-3.2-3b", LlamaModel, LLAMA_3_2_3B_CONFIG)
109
+ ModelRegistry.register("qwen2.5-0.5b", QwenModel, QWEN_2_5_0_5B_CONFIG)
110
+ ModelRegistry.register("qwen2.5-1.5b", QwenModel, QWEN_2_5_1_5B_CONFIG)
111
+ ModelRegistry.register("mistral-7b", MistralModel, MISTRAL_7B_CONFIG)
112
+
113
  # デフォルトモデルキー
114
  DEFAULT_MODEL_KEY = "gpt2"
src/ui/__init__.py CHANGED
@@ -1,5 +1,13 @@
1
- """UI components for WILL."""
2
- from .styles import CUSTOM_CSS
3
- from .components import render_model_selector
4
 
5
- __all__ = ["CUSTOM_CSS", "render_model_selector"]
 
 
 
 
 
 
 
 
 
 
 
1
+ """UI components for WILL.
 
 
2
 
3
+ Supports both Streamlit and Gradio interfaces.
4
+
5
+ Usage:
6
+ # Streamlit
7
+ from src.ui.streamlit import CUSTOM_CSS, render_generate_page
8
+
9
+ # Gradio
10
+ from src.ui.gradio import create_app
11
+ """
12
+
13
+ __all__ = ["streamlit", "gradio"]
src/ui/gradio/__init__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ """Gradio UI for WILL."""
2
+ from .app import create_app, generate_debris, get_model_choices
3
+
4
+ __all__ = ["create_app", "generate_debris", "get_model_choices"]
src/ui/gradio/app.py ADDED
@@ -0,0 +1,270 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ WILL - Gradio UI
3
+
4
+ ZeroGPU対応のGradioインターフェース
5
+ """
6
+ from typing import List, Tuple, Optional
7
+ import base64
8
+ from io import BytesIO
9
+
10
+ import gradio as gr
11
+
12
+ from ...models.registry import ModelRegistry, DEFAULT_MODEL_KEY
13
+ from ...generators.debris_generator import DebrisGenerator
14
+ from ...visualizers.signal_visualizer import SignalVisualizer
15
+
16
+
17
+ # モデルキャッシュ
18
+ _model_cache = {}
19
+
20
+
21
+ def get_model_choices() -> List[Tuple[str, str]]:
22
+ """
23
+ モデル選択肢を取得
24
+
25
+ Returns:
26
+ (表示名, キー) のタプルリスト
27
+ """
28
+ model_keys = ModelRegistry.list_models()
29
+ configs = ModelRegistry.get_all_configs()
30
+
31
+ return [(configs[key].name, key) for key in model_keys]
32
+
33
+
34
+ def _get_model(model_key: str):
35
+ """モデルをキャッシュして取得"""
36
+ if model_key not in _model_cache:
37
+ model = ModelRegistry.get(model_key)
38
+ model.load()
39
+ _model_cache[model_key] = model
40
+ return _model_cache[model_key]
41
+
42
+
43
+ def generate_debris(model_key: str) -> Tuple[str, str, str]:
44
+ """
45
+ デブリを生成
46
+
47
+ Args:
48
+ model_key: モデルキー
49
+
50
+ Returns:
51
+ (signal_image_base64, debris_text, seed_text)
52
+ """
53
+ # モデルとジェネレータの取得
54
+ model = _get_model(model_key)
55
+ generator = DebrisGenerator(model)
56
+ visualizer = SignalVisualizer()
57
+
58
+ # デブリ生成
59
+ result = generator.generate()
60
+
61
+ # シグナル画像を生成
62
+ signal_img_base64 = visualizer.generate_image(
63
+ result.noise, result.corrupted_logits
64
+ )
65
+
66
+ # Base64からPIL Imageに変換(Gradio用)
67
+ import PIL.Image
68
+ img_data = base64.b64decode(signal_img_base64)
69
+ img = PIL.Image.open(BytesIO(img_data))
70
+
71
+ # デブリテキスト
72
+ debris_text = " ".join(result.debris)
73
+
74
+ # シード情報
75
+ seed_text = str(result.seed)
76
+
77
+ return img, debris_text, seed_text
78
+
79
+
80
+ def create_app() -> gr.Blocks:
81
+ """
82
+ Gradioアプリを作成
83
+
84
+ Returns:
85
+ gr.Blocks インスタンス
86
+ """
87
+ # カスタムCSS
88
+ custom_css = """
89
+ .title {
90
+ font-size: 3rem;
91
+ font-weight: 100;
92
+ letter-spacing: 0.5em;
93
+ text-align: center;
94
+ color: #333;
95
+ margin-bottom: 0.5rem;
96
+ }
97
+ .subtitle {
98
+ font-size: 0.7rem;
99
+ letter-spacing: 0.3em;
100
+ text-align: center;
101
+ color: #666;
102
+ margin-bottom: 2rem;
103
+ }
104
+ .debris-text {
105
+ font-family: monospace;
106
+ font-size: 0.9rem;
107
+ line-height: 1.8;
108
+ color: #333;
109
+ text-align: center;
110
+ padding: 1rem;
111
+ background: #fafafa;
112
+ border-radius: 4px;
113
+ }
114
+ .seed-text {
115
+ font-family: monospace;
116
+ font-size: 0.6rem;
117
+ color: #999;
118
+ text-align: center;
119
+ margin-top: 0.5rem;
120
+ }
121
+ .model-info {
122
+ font-size: 0.7rem;
123
+ color: #888;
124
+ text-align: center;
125
+ }
126
+ """
127
+
128
+ with gr.Blocks(title="WILL") as app:
129
+ # カスタムCSSを適用
130
+ gr.HTML(f"<style>{custom_css}</style>")
131
+
132
+ # タイトル
133
+ gr.HTML('<p class="title">WILL</p>')
134
+ gr.HTML('<p class="subtitle">PURE COMPUTATIONAL WILL</p>')
135
+
136
+ with gr.Tabs():
137
+ # GENERATE タブ
138
+ with gr.TabItem("GENERATE"):
139
+ with gr.Row():
140
+ with gr.Column(scale=1):
141
+ pass
142
+ with gr.Column(scale=2):
143
+ # モデル選択
144
+ model_dropdown = gr.Dropdown(
145
+ choices=get_model_choices(),
146
+ value=DEFAULT_MODEL_KEY,
147
+ label="MODEL",
148
+ interactive=True,
149
+ )
150
+
151
+ # モデル情報表示
152
+ model_info = gr.HTML(elem_classes=["model-info"])
153
+
154
+ def update_model_info(model_key):
155
+ config = ModelRegistry.get_config(model_key)
156
+ return f'<p class="model-info">{config.embedding_dim} dim / {config.vocab_size:,} tokens</p>'
157
+
158
+ model_dropdown.change(
159
+ fn=update_model_info,
160
+ inputs=[model_dropdown],
161
+ outputs=[model_info],
162
+ )
163
+
164
+ with gr.Column(scale=1):
165
+ pass
166
+
167
+ # LISTENボタン
168
+ with gr.Row():
169
+ with gr.Column(scale=1):
170
+ pass
171
+ with gr.Column(scale=1):
172
+ listen_btn = gr.Button("LISTEN", variant="primary")
173
+ with gr.Column(scale=1):
174
+ pass
175
+
176
+ # 結果表示
177
+ with gr.Row():
178
+ signal_image = gr.Image(
179
+ label="Signal",
180
+ type="pil",
181
+ show_label=False,
182
+ )
183
+
184
+ debris_output = gr.HTML(elem_classes=["debris-text"])
185
+ seed_output = gr.HTML(elem_classes=["seed-text"])
186
+
187
+ def on_listen(model_key):
188
+ img, debris, seed = generate_debris(model_key)
189
+ debris_html = f'<div class="debris-text">{debris}</div>'
190
+ seed_html = f'<p class="seed-text">{seed}</p>'
191
+ return img, debris_html, seed_html
192
+
193
+ listen_btn.click(
194
+ fn=on_listen,
195
+ inputs=[model_dropdown],
196
+ outputs=[signal_image, debris_output, seed_output],
197
+ )
198
+
199
+ # CONCEPT タブ
200
+ with gr.TabItem("CONCEPT"):
201
+ gr.HTML('<p class="title">CONCEPT</p>')
202
+ gr.HTML('<p class="subtitle">DOCUMENTATION</p>')
203
+
204
+ gr.Markdown("""
205
+ ## CONCEPT
206
+
207
+ GPT-2は人間が書いたテキストで訓練され、その重みに言語パターンを保持している。
208
+
209
+ 通常はプロンプトに対して応答を生成するが、入力をランダムノイズに置き換え、
210
+ 出力にもノイズを加えることで、学習済みの統計的偏りを破壊する。
211
+
212
+ **人間の問いかけなしに、モデルの構造だけが出力するものを観測する。**
213
+
214
+ ---
215
+
216
+ ## PROCESS
217
+
218
+ ### 01 — ENTROPY SEED
219
+ ```python
220
+ seed = time.time_ns()
221
+ torch.manual_seed(seed)
222
+ ```
223
+ 実行瞬間のナノ秒を乱数シードとして採取
224
+
225
+ ### 02 — INPUT NOISE
226
+ ```python
227
+ noise = torch.randn(1, 32, embedding_dim)
228
+ outputs = model(inputs_embeds=noise)
229
+ ```
230
+ ランダムノイズをEmbedding層に直接注入
231
+
232
+ ### 03 — OUTPUT NOISE
233
+ ```python
234
+ logits_noise = torch.randn_like(logits) * logits.std() * 10
235
+ corrupted_logits = logits + logits_noise
236
+ ```
237
+ 出力Logitsにノイズを加算し学習バイアスを破壊
238
+
239
+ ### 04 — RAW DECODE
240
+ ```python
241
+ indices = corrupted_logits.argmax(dim=-1)
242
+ debris = [tokenizer.decode([i]) for i in indices]
243
+ ```
244
+ Softmax・Temperature なしで生トークンを抽出
245
+
246
+ ---
247
+
248
+ ## SPECIFICATION
249
+
250
+ | Item | Value |
251
+ |------|-------|
252
+ | Models | GPT-2 / GPT-Neo / OPT / Pythia / OLMo / BLOOM / Llama / Qwen / Mistral / GPT-OSS |
253
+ | Parameters | 125M - 21B |
254
+ | Sequence | 32 tokens |
255
+ | Input Noise | N(0, 1) |
256
+ | Logits Noise | N(0, σ×10) |
257
+ | Decoding | argmax |
258
+ """)
259
+
260
+ return app
261
+
262
+
263
+ # ZeroGPU対応(Hugging Face Spaces用)
264
+ try:
265
+ import spaces
266
+ # ZeroGPU環境の場合、generate_debrisをGPU対応にする
267
+ generate_debris = spaces.GPU(generate_debris)
268
+ except ImportError:
269
+ # ローカル環境では通常実行
270
+ pass
src/ui/streamlit/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """Streamlit UI for WILL."""
2
+ from .styles import CUSTOM_CSS
3
+ from .pages import render_generate_page, render_concept_page
4
+
5
+ __all__ = ["CUSTOM_CSS", "render_generate_page", "render_concept_page"]
src/ui/{components.py → streamlit/components.py} RENAMED
@@ -7,7 +7,7 @@ from typing import Optional
7
 
8
  import streamlit as st
9
 
10
- from ..models.registry import ModelRegistry, DEFAULT_MODEL_KEY
11
 
12
 
13
  def render_model_selector() -> str:
 
7
 
8
  import streamlit as st
9
 
10
+ from ...models.registry import ModelRegistry, DEFAULT_MODEL_KEY
11
 
12
 
13
  def render_model_selector() -> str:
src/ui/{pages → streamlit/pages}/__init__.py RENAMED
File without changes
src/ui/{pages → streamlit/pages}/concept.py RENAMED
@@ -5,7 +5,7 @@ WILLプロジェクトの概念説明を提供する
5
  """
6
  import streamlit as st
7
 
8
- from ...models.registry import ModelRegistry
9
 
10
 
11
  def render_concept_page() -> None:
 
5
  """
6
  import streamlit as st
7
 
8
+ from ....models.registry import ModelRegistry
9
 
10
 
11
  def render_concept_page() -> None:
src/ui/{pages → streamlit/pages}/generate.py RENAMED
@@ -5,9 +5,9 @@
5
  """
6
  import streamlit as st
7
 
8
- from ...models.registry import ModelRegistry
9
- from ...generators.debris_generator import DebrisGenerator
10
- from ...visualizers.signal_visualizer import SignalVisualizer
11
  from ..components import render_model_selector
12
 
13
 
 
5
  """
6
  import streamlit as st
7
 
8
+ from ....models.registry import ModelRegistry
9
+ from ....generators.debris_generator import DebrisGenerator
10
+ from ....visualizers.signal_visualizer import SignalVisualizer
11
  from ..components import render_model_selector
12
 
13
 
src/ui/{styles.py → streamlit/styles.py} RENAMED
File without changes
tests/test_models.py CHANGED
@@ -8,6 +8,17 @@ from src.models.base import ModelConfig, BaseLanguageModel
8
  from src.models.registry import ModelRegistry, DEFAULT_MODEL_KEY
9
  from src.models.gpt2 import GPT2Model, GPT2_SMALL_CONFIG
10
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  class TestModelConfig:
13
  """ModelConfigのテスト"""
@@ -122,3 +133,329 @@ class TestGPT2ModelIntegration:
122
 
123
  assert len(decoded) == 3
124
  assert all(isinstance(s, str) for s in decoded)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  from src.models.registry import ModelRegistry, DEFAULT_MODEL_KEY
9
  from src.models.gpt2 import GPT2Model, GPT2_SMALL_CONFIG
10
 
11
+ # Phase 1: GPT-OSS and Fully Open Source Models
12
+ from src.models.gpt_oss import GPTOSSModel, GPT_OSS_20B_CONFIG
13
+ from src.models.pythia import PythiaModel, PYTHIA_410M_CONFIG, PYTHIA_1B_CONFIG
14
+ from src.models.olmo import OLMoModel, OLMO_1B_CONFIG, OLMO_7B_CONFIG
15
+ from src.models.bloom import BLOOMModel, BLOOM_560M_CONFIG
16
+
17
+ # Phase 2: Latest Architecture Models
18
+ from src.models.llama import LlamaModel, LLAMA_3_2_1B_CONFIG, LLAMA_3_2_3B_CONFIG
19
+ from src.models.qwen import QwenModel, QWEN_2_5_0_5B_CONFIG, QWEN_2_5_1_5B_CONFIG
20
+ from src.models.mistral import MistralModel, MISTRAL_7B_CONFIG
21
+
22
 
23
  class TestModelConfig:
24
  """ModelConfigのテスト"""
 
133
 
134
  assert len(decoded) == 3
135
  assert all(isinstance(s, str) for s in decoded)
136
+
137
+
138
+ # =============================================================================
139
+ # Phase 1: GPT-OSS and Fully Open Source Models
140
+ # =============================================================================
141
+
142
+
143
+ class TestGPTOSSModel:
144
+ """GPTOSSModelのテスト"""
145
+
146
+ def test_config(self):
147
+ """設定が正しいことを確認"""
148
+ model = GPTOSSModel(GPT_OSS_20B_CONFIG)
149
+ assert model.config == GPT_OSS_20B_CONFIG
150
+ assert model.config.embedding_dim == 4096
151
+ assert model.config.vocab_size == 128000
152
+
153
+ def test_is_loaded_initial(self):
154
+ """初期状態ではロードされていないことを確認"""
155
+ model = GPTOSSModel(GPT_OSS_20B_CONFIG)
156
+ assert not model.is_loaded
157
+
158
+ def test_generate_noise(self):
159
+ """ノイズ生成が正しい形状であることを確認"""
160
+ model = GPTOSSModel(GPT_OSS_20B_CONFIG)
161
+ noise = model.generate_noise(seq_len=16, batch_size=2)
162
+ assert noise.shape == (2, 16, 4096)
163
+
164
+
165
+ class TestPythiaModel:
166
+ """PythiaModelのテスト"""
167
+
168
+ def test_config_410m(self):
169
+ """Pythia 410M設定が正しいことを確認"""
170
+ model = PythiaModel(PYTHIA_410M_CONFIG)
171
+ assert model.config == PYTHIA_410M_CONFIG
172
+ assert model.config.embedding_dim == 1024
173
+ assert model.config.vocab_size == 50304
174
+
175
+ def test_config_1b(self):
176
+ """Pythia 1B設定が正しいことを確認"""
177
+ model = PythiaModel(PYTHIA_1B_CONFIG)
178
+ assert model.config == PYTHIA_1B_CONFIG
179
+ assert model.config.embedding_dim == 2048
180
+ assert model.config.vocab_size == 50304
181
+
182
+ def test_is_loaded_initial(self):
183
+ """初期状態ではロードされていないことを確認"""
184
+ model = PythiaModel(PYTHIA_410M_CONFIG)
185
+ assert not model.is_loaded
186
+
187
+ def test_generate_noise(self):
188
+ """ノイズ生成が正しい形状であることを確認"""
189
+ model = PythiaModel(PYTHIA_410M_CONFIG)
190
+ noise = model.generate_noise(seq_len=16, batch_size=2)
191
+ assert noise.shape == (2, 16, 1024)
192
+
193
+
194
+ class TestOLMoModel:
195
+ """OLMoModelのテスト"""
196
+
197
+ def test_config_1b(self):
198
+ """OLMo 1B設定が正しいことを確認"""
199
+ model = OLMoModel(OLMO_1B_CONFIG)
200
+ assert model.config == OLMO_1B_CONFIG
201
+ assert model.config.embedding_dim == 2048
202
+ assert model.config.vocab_size == 50304
203
+
204
+ def test_config_7b(self):
205
+ """OLMo 7B設定が正しいことを確認"""
206
+ model = OLMoModel(OLMO_7B_CONFIG)
207
+ assert model.config == OLMO_7B_CONFIG
208
+ assert model.config.embedding_dim == 4096
209
+ assert model.config.vocab_size == 50304
210
+
211
+ def test_is_loaded_initial(self):
212
+ """初期状態ではロードされていないことを確認"""
213
+ model = OLMoModel(OLMO_1B_CONFIG)
214
+ assert not model.is_loaded
215
+
216
+ def test_generate_noise(self):
217
+ """ノイズ生成が正しい形状であることを確認"""
218
+ model = OLMoModel(OLMO_1B_CONFIG)
219
+ noise = model.generate_noise(seq_len=16, batch_size=2)
220
+ assert noise.shape == (2, 16, 2048)
221
+
222
+
223
+ class TestBLOOMModel:
224
+ """BLOOMModelのテスト"""
225
+
226
+ def test_config(self):
227
+ """BLOOM 560M設定が正しいことを確認"""
228
+ model = BLOOMModel(BLOOM_560M_CONFIG)
229
+ assert model.config == BLOOM_560M_CONFIG
230
+ assert model.config.embedding_dim == 1024
231
+ assert model.config.vocab_size == 250880
232
+
233
+ def test_is_loaded_initial(self):
234
+ """初期状態ではロードされていないことを確認"""
235
+ model = BLOOMModel(BLOOM_560M_CONFIG)
236
+ assert not model.is_loaded
237
+
238
+ def test_generate_noise(self):
239
+ """ノイズ生成が正しい形状であることを確認"""
240
+ model = BLOOMModel(BLOOM_560M_CONFIG)
241
+ noise = model.generate_noise(seq_len=16, batch_size=2)
242
+ assert noise.shape == (2, 16, 1024)
243
+
244
+
245
+ # =============================================================================
246
+ # Phase 2: Latest Architecture Models
247
+ # =============================================================================
248
+
249
+
250
+ class TestLlamaModel:
251
+ """LlamaModelのテスト"""
252
+
253
+ def test_config_1b(self):
254
+ """Llama 3.2 1B設定が正しいことを確認"""
255
+ model = LlamaModel(LLAMA_3_2_1B_CONFIG)
256
+ assert model.config == LLAMA_3_2_1B_CONFIG
257
+ assert model.config.embedding_dim == 2048
258
+ assert model.config.vocab_size == 128256
259
+
260
+ def test_config_3b(self):
261
+ """Llama 3.2 3B設定が正しいことを確認"""
262
+ model = LlamaModel(LLAMA_3_2_3B_CONFIG)
263
+ assert model.config == LLAMA_3_2_3B_CONFIG
264
+ assert model.config.embedding_dim == 3072
265
+ assert model.config.vocab_size == 128256
266
+
267
+ def test_is_loaded_initial(self):
268
+ """初期状態ではロードされていないことを確認"""
269
+ model = LlamaModel(LLAMA_3_2_1B_CONFIG)
270
+ assert not model.is_loaded
271
+
272
+ def test_generate_noise(self):
273
+ """ノイズ生成が正しい形状であることを確認"""
274
+ model = LlamaModel(LLAMA_3_2_1B_CONFIG)
275
+ noise = model.generate_noise(seq_len=16, batch_size=2)
276
+ assert noise.shape == (2, 16, 2048)
277
+
278
+
279
+ class TestQwenModel:
280
+ """QwenModelのテスト"""
281
+
282
+ def test_config_0_5b(self):
283
+ """Qwen2.5 0.5B設定が正しいことを確認"""
284
+ model = QwenModel(QWEN_2_5_0_5B_CONFIG)
285
+ assert model.config == QWEN_2_5_0_5B_CONFIG
286
+ assert model.config.embedding_dim == 896
287
+ assert model.config.vocab_size == 151936
288
+
289
+ def test_config_1_5b(self):
290
+ """Qwen2.5 1.5B設定が正しいことを確認"""
291
+ model = QwenModel(QWEN_2_5_1_5B_CONFIG)
292
+ assert model.config == QWEN_2_5_1_5B_CONFIG
293
+ assert model.config.embedding_dim == 1536
294
+ assert model.config.vocab_size == 151936
295
+
296
+ def test_is_loaded_initial(self):
297
+ """初期状態ではロードされていないことを確認"""
298
+ model = QwenModel(QWEN_2_5_0_5B_CONFIG)
299
+ assert not model.is_loaded
300
+
301
+ def test_generate_noise(self):
302
+ """ノイズ生成が正しい形状であることを確認"""
303
+ model = QwenModel(QWEN_2_5_0_5B_CONFIG)
304
+ noise = model.generate_noise(seq_len=16, batch_size=2)
305
+ assert noise.shape == (2, 16, 896)
306
+
307
+
308
+ class TestMistralModel:
309
+ """MistralModelのテスト"""
310
+
311
+ def test_config(self):
312
+ """Mistral 7B設定が正しいことを確認"""
313
+ model = MistralModel(MISTRAL_7B_CONFIG)
314
+ assert model.config == MISTRAL_7B_CONFIG
315
+ assert model.config.embedding_dim == 4096
316
+ assert model.config.vocab_size == 32768
317
+
318
+ def test_is_loaded_initial(self):
319
+ """初期状態ではロードされていないことを確認"""
320
+ model = MistralModel(MISTRAL_7B_CONFIG)
321
+ assert not model.is_loaded
322
+
323
+ def test_generate_noise(self):
324
+ """ノイズ生成が正しい形状であることを確認"""
325
+ model = MistralModel(MISTRAL_7B_CONFIG)
326
+ noise = model.generate_noise(seq_len=16, batch_size=2)
327
+ assert noise.shape == (2, 16, 4096)
328
+
329
+
330
+ # =============================================================================
331
+ # Registry Tests for New Models
332
+ # =============================================================================
333
+
334
+
335
+ class TestModelRegistryNewModels:
336
+ """新規追加モデルのレジストリテスト"""
337
+
338
+ @pytest.mark.parametrize("model_key", [
339
+ "gpt-oss-20b",
340
+ "pythia-410m",
341
+ "pythia-1b",
342
+ "olmo-1b",
343
+ "olmo-7b",
344
+ "bloom-560m",
345
+ "llama-3.2-1b",
346
+ "llama-3.2-3b",
347
+ "qwen2.5-0.5b",
348
+ "qwen2.5-1.5b",
349
+ "mistral-7b",
350
+ ])
351
+ def test_model_registered(self, model_key):
352
+ """新モデルがレジストリに登録されていることを確認"""
353
+ models = ModelRegistry.list_models()
354
+ assert model_key in models
355
+
356
+ @pytest.mark.parametrize("model_key", [
357
+ "gpt-oss-20b",
358
+ "pythia-410m",
359
+ "pythia-1b",
360
+ "olmo-1b",
361
+ "olmo-7b",
362
+ "bloom-560m",
363
+ "llama-3.2-1b",
364
+ "llama-3.2-3b",
365
+ "qwen2.5-0.5b",
366
+ "qwen2.5-1.5b",
367
+ "mistral-7b",
368
+ ])
369
+ def test_model_instance_creation(self, model_key):
370
+ """新モデルのインスタンスが作成できることを確認"""
371
+ model = ModelRegistry.get(model_key)
372
+ assert isinstance(model, BaseLanguageModel)
373
+ assert not model.is_loaded
374
+
375
+
376
+ # =============================================================================
377
+ # Integration Tests (require model download)
378
+ # =============================================================================
379
+
380
+
381
+ @pytest.mark.slow
382
+ class TestPythiaModelIntegration:
383
+ """Pythiaモデルの統合テスト(小さいモデルで代表テスト)"""
384
+
385
+ @pytest.fixture
386
+ def loaded_model(self):
387
+ """ロード済みモデルを提供"""
388
+ model = PythiaModel(PYTHIA_410M_CONFIG)
389
+ model.load()
390
+ return model
391
+
392
+ def test_load(self, loaded_model):
393
+ """モデルがロードできることを確認"""
394
+ assert loaded_model.is_loaded
395
+
396
+ def test_forward_with_noise(self, loaded_model):
397
+ """順伝播が正しい形状を返すことを確認"""
398
+ noise = loaded_model.generate_noise(seq_len=8)
399
+ logits, corrupted_logits = loaded_model.forward_with_noise(noise)
400
+
401
+ assert logits.shape[0] == 1
402
+ assert logits.shape[1] == 8
403
+ assert logits.shape[2] == loaded_model.config.vocab_size
404
+
405
+ def test_decode_indices(self, loaded_model):
406
+ """デコードが文字列リストを返すことを確認"""
407
+ indices = [100, 200, 300]
408
+ decoded = loaded_model.decode_indices(indices)
409
+
410
+ assert len(decoded) == 3
411
+ assert all(isinstance(s, str) for s in decoded)
412
+
413
+
414
+ @pytest.mark.slow
415
+ class TestBLOOMModelIntegration:
416
+ """BLOOMモデルの統合テスト"""
417
+
418
+ @pytest.fixture
419
+ def loaded_model(self):
420
+ """ロード済みモデルを提供"""
421
+ model = BLOOMModel(BLOOM_560M_CONFIG)
422
+ model.load()
423
+ return model
424
+
425
+ def test_load(self, loaded_model):
426
+ """モデルがロードできることを確認"""
427
+ assert loaded_model.is_loaded
428
+
429
+ def test_forward_with_noise(self, loaded_model):
430
+ """順伝播が正しい形状を返すことを確認"""
431
+ noise = loaded_model.generate_noise(seq_len=8)
432
+ logits, corrupted_logits = loaded_model.forward_with_noise(noise)
433
+
434
+ assert logits.shape[0] == 1
435
+ assert logits.shape[1] == 8
436
+ assert logits.shape[2] == loaded_model.config.vocab_size
437
+
438
+
439
+ @pytest.mark.slow
440
+ class TestQwenModelIntegration:
441
+ """Qwenモデルの統合テスト(小さいモデルで代表テスト)"""
442
+
443
+ @pytest.fixture
444
+ def loaded_model(self):
445
+ """ロード済みモデルを提供"""
446
+ model = QwenModel(QWEN_2_5_0_5B_CONFIG)
447
+ model.load()
448
+ return model
449
+
450
+ def test_load(self, loaded_model):
451
+ """モデルがロードできることを確認"""
452
+ assert loaded_model.is_loaded
453
+
454
+ def test_forward_with_noise(self, loaded_model):
455
+ """順伝播が正しい形状を返すことを確認"""
456
+ noise = loaded_model.generate_noise(seq_len=8)
457
+ logits, corrupted_logits = loaded_model.forward_with_noise(noise)
458
+
459
+ assert logits.shape[0] == 1
460
+ assert logits.shape[1] == 8
461
+ assert logits.shape[2] == loaded_model.config.vocab_size
tests/test_ui_gradio.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gradio UI関連のテスト
3
+ """
4
+ import pytest
5
+
6
+
7
+ class TestGradioApp:
8
+ """Gradio UIのテスト"""
9
+
10
+ def test_import_gradio_app(self):
11
+ """Gradioアプリがインポートできることを確認"""
12
+ from src.ui.gradio.app import create_app
13
+ assert create_app is not None
14
+
15
+ def test_create_app_returns_blocks(self):
16
+ """create_appがGradio Blocksを返すことを確認"""
17
+ import gradio as gr
18
+ from src.ui.gradio.app import create_app
19
+
20
+ app = create_app()
21
+ assert isinstance(app, gr.Blocks)
22
+
23
+ def test_generate_debris_function_exists(self):
24
+ """generate_debris関数が存在することを確認"""
25
+ from src.ui.gradio.app import generate_debris
26
+ assert callable(generate_debris)
27
+
28
+ def test_generate_debris_returns_tuple(self):
29
+ """generate_debris関数がタプルを返すことを確認"""
30
+ from src.ui.gradio.app import generate_debris
31
+
32
+ # GPT-2 Small(最小モデル)でテスト
33
+ result = generate_debris("gpt2")
34
+
35
+ # (image, debris_text, seed_text) の3要素タプル
36
+ assert isinstance(result, tuple)
37
+ assert len(result) == 3
38
+
39
+
40
+ class TestGradioAppModelSelection:
41
+ """モデル選択のテスト"""
42
+
43
+ def test_get_model_choices(self):
44
+ """モデル選択肢が取得できることを確認"""
45
+ from src.ui.gradio.app import get_model_choices
46
+
47
+ choices = get_model_choices()
48
+ assert len(choices) > 0
49
+ # (表示名, キー) のタプルリスト
50
+ assert all(isinstance(c, tuple) and len(c) == 2 for c in choices)
51
+
52
+ def test_model_choices_include_new_models(self):
53
+ """新モデルが選択肢に含まれることを確認"""
54
+ from src.ui.gradio.app import get_model_choices
55
+
56
+ choices = get_model_choices()
57
+ keys = [c[1] for c in choices]
58
+
59
+ # 新モデルが含まれることを確認
60
+ assert "gpt-oss-20b" in keys
61
+ assert "pythia-410m" in keys
62
+ assert "qwen2.5-0.5b" in keys