Verification of operation when LLM is not specified in distributed brain simulation
Copyright: 2026 Moonlight Technologies Inc. All Rights Reserved.
Author: Masahiro Aoki
Implementation notes (artifacts): See
docs/implementation/ARTIFACT_MANIFESTS.mdfor theartifact_manifest.jsonoutput by the training script and recommended CLI flags.
Verification date
January 8, 2026
1. Overview
In a distributed brain simulation system, we verified the operational flow when no LLM model is explicitly specified for a node.
Supplementary note: With the node type-based LLM training pipeline introduced in February 2026, the
node_typemeta is added to the artifact generated when--node-typeis specified during training, and AutoModelSelector refers to this to assign an appropriate model to each node. The verification examples in this document also correspond to this new specification.
In the current implementation, the following layers of processing ensure that each node functions properly: 1. Frontend layer - Model specified default processing 2. ZenohBrainNode layer - automatic model selector 3. AutoModelSelector layer - Fallback mechanism
Purpose and use of this document
- Purpose: Verify behavior when LLM is not specified and share default/fallback behavior.
- Target audience: Distributed brain node implementers, QA, and operations personnel.
- First reading order: Overview → Architecture Verification → Failure/Fallback Observation → Countermeasures.
- Related links: Examples/run_zenoh_distributed_brain.py for distributed brain execution script, implementation/PFC_ZENOH_EXECUTIVE.md for PFC/Zenoh/Executive details.
2. Architecture verification
2.1 Frontend → Backend pipeline
File: frontend/pages/distributed_brain.py (lines 960-990)
# Building model settings
model_config = {
str(n.get('rank')): n.get('model_artifact_id')
for n in flat_node_list
if n.get('model_artifact_id') # Only nodes with LLM specified
}
model_config_json = json.dumps(model_config)
Characteristics:
- ✅ Nodes without model specification are not included in model_config
- ✅ Each node only receives command line arguments --node-id and --module-type
- ✅ model_artifact_id is not passed explicitly
2.2 Node startup flow
File: frontend/pages/distributed_brain.py (lines 1010-1050)
command_list = [
'python', '-u', script_path,
'--node-id', node_id, # e.g., "lang-main-0"
'--module-type', node_type_lower # e.g., "lang-main"
]
Information passed at startup:
- node_id: Node identifier
- module_type: Node functional type
- Model parameters are not passed ← Important
3. Loading model with ZenohBrainNode
3.1 Initialization sequence
File: examples/run_zenoh_distributed_brain.py (line 1150)
config = {"d_model": 128} # Default settings
node = ZenohBrainNode(args.node_id, args.module_type, config)
node.start()
Works:
1. Create a node based on module_type
2. config only contains hyperparameters (d_model, etc.)
3. Model is automatically generated with _create_model()
3.2 Model generation method
File: examples/run_zenoh_distributed_brain.py (lines 245-275)
def _create_model(self) -> SNNModel:
"""Create neural model for this node using AutoModelSelector."""
session_id = self._get_latest_model_session()
try:
# Automatic model selection with AutoModelSelector
model = AutoModelSelector.get_model(
task_type=self.module_type, # "lang-main", "visual", etc.
session_id=session_id, # Get from DB
api_client=self.client,
d_model=self.config.get("d_model", 128)
)
# Load tokenizer (for lang-main)
if self.module_type == "lang-main" and session_id:
self._load_tokenizer_from_session(session_id)
return model
except Exception as e:
self.logger.error(f"AutoModelSelector failed: {e}. Falling back...")
# Fallback: Default initialization of SpikingEvoTextLM
vocab_size = 30522
return SpikingEvoTextLM(vocab_size=vocab_size, d_model=128)
Processing flow:
1. Get the latest session from API: _get_latest_model_session()
- Find the latest model session ID stored in the DB
- session_id = None on failure
- Automatic selection with AutoModelSelector:
session_idis valid → Download model from DB-
session_idis None → initialized with default parameters -
Loading Tokenizer (lang-main node only)
-
Fallback:
- Model loading failure → Initialize with default model
- SpikingEvoTextLM(vocab_size=30522, d_model=128)
4. Detailed operation of AutoModelSelector
4.1 Model class mapping
File: evospikenet/model_selector.py (lines 87-99)
@staticmethod
def _get_model_class(task_type: str):
if task_type == 'text' or task_type == 'lang-main':
return SpikingEvoTextLM
elif task_type == 'vision' or task_type == 'visual':
return SpikingEvoVisionEncoder
elif task_type == 'audio' or task_type == 'auditory':
return SpikingEvoAudioEncoder
elif task_type == 'multimodal':
return SpikingEvoMultiModalLM
return None
Characteristics:
- ✅ Determine the corresponding model class from module_type
- ✅ Supports multiple aliases ("lang-main" = "text")
- ✅ Return None for unknown type
4.2 Default parameters
File: evospikenet/model_selector.py (lines 102-131)
@staticmethod
def _get_default_params(task_type: str):
"""Returns robust default parameters for each model type."""
common = {'time_steps': 10}
if task_type in ['text', 'lang-main']:
return {
'vocab_size': 30522,
'd_model': 128,
'n_heads': 4,
'num_transformer_blocks': 2,
**common
}
elif task_type in ['vision', 'visual']:
return {
'input_channels': 1,
'output_dim': 128,
'image_size': (28, 28),
**common
}
elif task_type in ['audio', 'auditory']:
return {
'input_features': 13, # MFCC
'output_neurons': 128,
**common
}
# ...
Characteristics: - ✅ Define robust default values for each node type - ✅ Automatic initialization with these parameters if LLM is not specified - ✅ Settings optimized for node functions
4.3 Model loading flow (acquisition from API)
File: evospikenet/model_selector.py (lines 56-75)
@staticmethod
def get_model(task_type: str, session_id: str = None,
api_client=None, device=None, **kwargs):
"""
Factory method to get an initialized model instance.
"""
device = device or AutoModelSelector.get_device()
# 1. Decide the model class
model_class = AutoModelSelector._get_model_class(task_type)
if not model_class:
raise ValueError(f"Unknown task_type: {task_type}")
# 2. Attempt to load from API (if session_id exists)
if session_id and api_client:
try:
return AutoModelSelector._load_from_api(
model_class, task_type, session_id,
api_client, device
)
except Exception as e:
logger.error(f"Failed to load from API: {e}. "
f"Falling back to default initialization.")
# 3. Fallback: default initialization
logger.info(f"Initializing {model_class.__name__} "
f"with default/provided parameters.")
params = AutoModelSelector._get_default_params(task_type)
params.update(kwargs) # Can be overridden with CLI arguments
model = model_class(**params).to(device)
return model
Process flow diagram:``` get_model(task_type, session_id=None/有効, api_client) │ ├─ task_type から モデルクラス決定 │ ├─ session_id が有効か? │ ├─ YES: API から artifact ダウンロード │ │ ├─ config.json, weights 取得 │ │ └─ モデル復元 │ │ │ └─ NO または 取得失敗 │ └─ フォールバック │ └─ デフォルトパラメータで初期化 └─ model_class(**params).to(device)
---
## 5. Implementation fallback mechanism
### 5.1 Gradual fallback
**Stage 1**: Download model from DB```python
if session_id and api_client:
return AutoModelSelector._load_from_api(...)
Stage 2: When API connection fails```python except Exception as e: logger.error(f"Failed to load from API: {e}")
**Stage 3**: Default initialization```python
params = AutoModelSelector._get_default_params(task_type)
model = model_class(**params).to(device)
Stage 4: ZenohBrainNode fallback```python except Exception as e: self.logger.error(f"AutoModelSelector failed: {e}") return SpikingEvoTextLM(vocab_size=30522, d_model=128)
### 5.2 Parameter override mechanism
**File**: `evospikenet/model_selector.py` (line 75)
```python
params.update(kwargs) # Can be overridden with CLI arguments or config
Usage example:```python AutoModelSelector.get_model( task_type="lang-main", session_id=None, api_client=None, d_model=256, # Overwrite with kwargs n_heads=8 )
---
## 6. Database integration verification
### 6.1 Get session ID
**File**: `examples/run_zenoh_distributed_brain.py` (lines 192-210)
```python
def _get_latest_model_session(self):
"""Find the session ID of the latest model artifact."""
try:
response = requests.get(
f"{self.api_base_url}/api/artifacts",
params={"artifact_type": "model"},
timeout=5
)
if response.status_code == 200:
artifacts = response.json()
# Filter for weights file
model_artifacts = [
a for a in artifacts
if a['name'] == 'spiking_lm.pth'
]
if model_artifacts:
# Sort by creation time desc
model_artifacts.sort(
key=lambda x: x['created_at'],
reverse=True
)
# Return latest session_id
return model_artifacts[0]['session_id']
except requests.exceptions.RequestException as e:
self.logger.warning(f"Failed to fetch artifacts: {e}")
return None # No session found
Works:
- ✅ Get all model artifacts from API
- ✅ Extract latest session ID
- ✅ Return None on failure
6.2 Artifact Download
File: evospikenet/model_selector.py (lines 134-170)
@staticmethod
def _load_from_api(model_class, task_type, session_id,
api_client, device):
"""Helper to download config and weights, then load the model."""
# Determine artifact names based on task type
config_name = "config.json"
if task_type in ['text', 'lang-main']:
weights_name = "spiking_lm.pth"
elif task_type in ['vision', 'visual']:
weights_name = "vision_encoder.pth"
elif task_type in ['audio', 'auditory']:
weights_name = "audio_encoder.pth"
elif task_type == 'multimodal':
weights_name = "multi_modal_lm.pth"
else:
# Fallback to generic weights
weights_name = "model.pth"
# Download config and weights (the helper uses a simple cache directory
# under `/tmp/evospikenet_cache/{session_id}` so repeated invocations do not
# re-fetch already retrieved files).
config_path = AutoModelSelector._download_artifact(
api_client, session_id, config_name
)
weights_path = AutoModelSelector._download_artifact(
api_client, session_id, weights_name
)
# Load model from saved weights
# (Implementation details omitted for brevity)
Characteristics:
- ✅ Determine appropriate artifact name based on task_type
- ✅ Download config and weights from API
- ✅ Exception occurs when download fails
7. Operation confirmation by node type
7.1 Lang-Main Node
| Item | Operation | Confirmation status |
|---|---|---|
| Model specified | Load SpikingEvoTextLM from DB | ✅ Implemented |
| No model specified | Initialized with default SpikingEvoTextLM(vocab_size=30522) | ✅ Implemented |
| Tokenizer | Load BERT tokenizer from DB, use bert-base-uncased in case of failure |
✅ With fallback mechanism |
| API connection failure | Default initialization in memory | ✅ Implemented |
7.2 Visual Node
| Item | Operation | Confirmation status |
|---|---|---|
| Model specified | Load SpikingEvoVisionEncoder from DB | ✅ Implemented |
| No model specified | Initialized with default SpikingEvoVisionEncoder(input_channels=1, image_size=(28,28)) | ✅ Implemented |
| API connection failure | Default initialization in memory | ✅ Implemented |
7.3 Audio Node
| Item | Operation | Confirmation status |
|---|---|---|
| Model specified | Load SpikingEvoAudioEncoder from DB | ✅ Implemented |
| No model specified | Initialized with default SpikingEvoAudioEncoder(input_features=13, output_neurons=128) | ✅ Implemented |
| API connection failure | Default initialization in memory | ✅ Implemented |
7.4 PFC Node
| Item | Operation | Confirmation status |
|---|---|---|
| Inference model | Initialized with PFCDecisionEngine or AdvancedPFCEngine (no LLM required) | ✅ LLM independent |
| Influenced by model specification | Not affected (dedicated model because it is for routing) | ✅ As designed |
7.5 Motor Node
| Item | Operation | Confirmation status |
|---|---|---|
| Model specified | Load MotorControlLM from DB (to be implemented) | 🔄 Phase 2 |
| Model not specified | Works with SimpleLIFNode or AutonomousMotorNode | ✅ Implemented |
8. Environment variables and settings
8.1 API URL settings
File: examples/run_zenoh_distributed_brain.py (lines 1150-1157)
self.api_base_url = os.environ.get("API_URL", "http://api:8000")
Default: http://api:8000
Setting method:```bash export API_URL="http://custom-api-server:8000" python examples/run_zenoh_distributed_brain.py \ --node-id lang-main-0 \ --module-type lang-main
### 8.2 Device automatic selection
**File**: `evospikenet/model_selector.py` (lines 31-37)
```python
@staticmethod
def get_device():
"""Auto-detects the best available device."""
if torch.cuda.is_available():
return 'cuda'
elif torch.backends.mps.is_available():
return 'mps'
else:
return 'cpu'
Priority order: CUDA > MPS (Metal) > CPU
9. Error handling and recovery strategies
9.1 Behavior when API connection fails
| Scenario | Implementation status | Behavior |
|---|---|---|
| DB is not started | ✅ | Default initialization |
| Failed to obtain session ID | ✅ | session_id = None → Fallback |
| Artifact download failure | ✅ | Exception catch → Default initialization |
| API Timeout | ✅ | Round up failures with a 5 second timeout |
9.2 Log output
File: evospikenet/model_selector.py (lines 55, 65)
logger.info(f"AutoModelSelector: Selected device '{device}'...")
logger.error(f"Failed to load from API: {e}...")
logger.info(f"Initializing {model_class.__name__}...")
Log level: - INFO: Normal processing flow (device selection, initialization) - ERROR: API connection failure - WARNING: DB connection failure (fallback execution)
10. Implementation safety evaluation
10.1 Robustness Checklist
- ✅ Initialization when LLM is not specified: Implemented for all node types
- ✅ Behavior when API connection fails: Guaranteed by default initialization
- ✅ Parameter validation: Guaranteed safe configuration with default values
- ✅ Device compatibility: Automatically select CUDA/MPS/CPU
- ✅ Error Handling: Gradual fallback mechanism
10.2 Performance considerations
| Processing | Estimated time | Impact |
|---|---|---|
| Get session ID from API | 100-500ms | Only once at startup |
| Artifact Download | 1-5s | API communication dependent |
| Model initialization | 100-500ms | Only once at startup |
| Default Initialization | 10-50ms | Fast Fallback |
Conclusion: Booting without specifying LLM is fast (within 50ms for default initialization)
11. Recommendations
11.1 Configuration in production environment
# Check that the API is always running
docker-compose up -d api
# start node
API_URL="http://api:8000" python examples/run_zenoh_distributed_brain.py \
--node-id lang-main-0 \
--module-type lang-main
11.2 Use in development/test environments
# Node can be started without API
python examples/run_zenoh_distributed_brain.py \
--node-id visual-0 \
--module-type visual
11.3 Monitoring logs
# Set log level to DEBUG and trace
export LOG_LEVEL=DEBUG
python examples/run_zenoh_distributed_brain.py ...
12. Conclusion
In a distributed brain simulation, the behavior when LLM was not explicitly specified for a node was confirmed as follows:
✅ Verification completed items
- Automatic fallback mechanism is fully implemented
- Automatically default initialization when reading from API fails
-
Ensure reliability with multi-step fallback mechanism
-
Default values optimized for each node type
- Lang-Main: SpikingEvoTextLM(vocab_size=30522)
- Visual: SpikingEvoVisionEncoder(image_size=(28,28))
-
Audio: SpikingEvoAudioEncoder(input_features=13)
-
Robust error handling
- Continues operation even when API connection fails
- Records detailed information in logs
-
Recoverable design
-
Implementation suitable for production use
- Timeout setting (5 seconds)
- Automatic device selection (CUDA/MPS/CPU)
- Gradual phase initialization
🎯 System robustness
Operation confirmation by scenario: - ✅ With DB and LLM: Load from DB - ✅ With DB and without LLM: Default initialization - ✅ Without DB, with LLM: Default initialization - ✅ No DB, no LLM: Default initialization
Conclusion: Initialized successfully in all scenarios Secure implementation