Text Generation
LiteRT
LiteRT
English
android-wear
wearos
cardiac-disease
medgemma
mobile-ai
ios-coreml
android-litert
conformer
micro-model
multimodal
cardiology
biosignal
ppg
Instructions to use litert-community/Cardiac_micro_model_Android_Wear with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- LiteRT
How to use litert-community/Cardiac_micro_model_Android_Wear with LiteRT:
# No code snippets available yet for this library. # To use this model, check the repository files and the library's documentation. # Want to help? PRs adding snippets are welcome at: # https://github.com/huggingface/huggingface.js
- Notebooks
- Google Colab
- Kaggle
File size: 7,778 Bytes
dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 201095b dafcd87 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | """
Comprehensive Test Suite for MedGemma-Micro Mobile (Sub-512MB Architecture)
============================================================================
Verifies:
1. Synthetic PPG waveform generator (all 5 rhythm classes, correct 90s shape).
2. 1D-Conformer Biosignal Encoder output shapes, temporal attention & gradients.
3. Temporal Cross-Attention Projector bridge with d_model=896 (Qwen2.5-0.5B).
4. On-device Clinical RAG (<25MB) retrieval precision and latency (<5ms).
5. Knowledge Distillation dual loss calculation.
6. Domain coverage across ACC/AHA & ESC guidelines and pharmacology safety.
7. Safetensors serialization and strict < 512 MB mobile budget assertion.
"""
import os
import sys
import time
import tempfile
import torch
import torch.nn as nn
import numpy as np
from pipeline import (
PPGSimulator,
SyntheticPPGDataset,
PPGWaveformEncoder,
PPGConformerEncoder,
PPGToLLMProjector,
PPGCrossAttentionProjector,
KnowledgeDistillationLoss,
MedGemmaMicroModel,
export_and_verify_checkpoint,
CardiologyDomainExpert,
)
from clinical_rag import ClinicalRAG, clinical_rag_engine
def test_ppg_simulator():
print("[TEST 1/7] Testing PPGSimulator across all 5 cardiac conditions...")
sim = PPGSimulator(sampling_rate=25, duration_sec=90)
for cond_idx, cond_name in PPGSimulator.CLASSES.items():
signal, label = sim.generate_window(cond_idx)
assert label == cond_idx
assert signal.shape == (2250, 1), f"Expected (2250, 1), got {signal.shape}"
assert not np.isnan(signal).any(), f"NaN detected in signal for condition {cond_name}"
assert not np.isinf(signal).any(), f"Inf detected in signal for condition {cond_name}"
print(" -> Passed: All 5 physiological conditions generated valid 90s waveforms.")
def test_conformer_encoder():
print("[TEST 2/7] Testing 1D-Conformer Biosignal Encoder (Attention + Depthwise CNN)...")
batch_size = 2
seq_len = 2250 # 90s @ 25Hz
channels = 1
encoder = PPGConformerEncoder(in_channels=channels, num_classes=5, latent_dim=256, num_layers=2)
dummy_input = torch.randn(batch_size, seq_len, channels)
logits, latent = encoder(dummy_input)
assert logits.shape == (batch_size, 5), f"Expected logits (2, 5), got {logits.shape}"
assert latent.shape == (batch_size, 256), f"Expected latent (2, 256), got {latent.shape}"
# Verify backward pass & gradients through Conformer blocks
loss = logits.sum() + latent.sum()
loss.backward()
for name, param in encoder.named_parameters():
if param.requires_grad:
assert param.grad is not None, f"Gradient missing for {name}"
print(" -> Passed: 1D-Conformer produces valid logits & pooled latents with full gradient flow.")
def test_cross_attention_projector():
print("[TEST 3/7] Testing Temporal Cross-Attention Projector Bridge (d_model = 896)...")
batch_size = 2
latent_dim = 256
llm_dim = 896 # Qwen2.5-0.5B hidden size
num_prefix_tokens = 4
projector = PPGCrossAttentionProjector(sensor_dim=latent_dim, llm_dim=llm_dim, num_prefix_tokens=num_prefix_tokens)
dummy_latent = torch.randn(batch_size, latent_dim)
prefix_embeds = projector(dummy_latent)
assert prefix_embeds.shape == (batch_size, num_prefix_tokens, llm_dim), (
f"Expected prefix shape ({batch_size}, {num_prefix_tokens}, {llm_dim}), got {prefix_embeds.shape}"
)
# Test backward pass
loss = prefix_embeds.sum()
loss.backward()
assert projector.queries.grad is not None
print(f" -> Passed: Cross-Attention correctly projects sensor latent to {num_prefix_tokens}x{llm_dim} tokens.")
def test_clinical_rag():
print("[TEST 4/7] Testing On-Device Clinical RAG Guidelines Engine (< 25MB)...")
t0 = time.perf_counter()
res = clinical_rag_engine.retrieve("Metoprolol dosing for atrial fibrillation", condition="Atrial Fibrillation (AFib)")
latency_ms = (time.perf_counter() - t0) * 1000.0
assert len(res) > 0, "RAG returned no results"
top_doc = res[0]
assert "AFib" in top_doc["title"] or "Atrial Fibrillation" in top_doc["title"], f"Unexpected top guideline: {top_doc['title']}"
assert top_doc["retrieval_score"] > 5.0, f"Expected high retrieval score, got {top_doc['retrieval_score']}"
ctx = clinical_rag_engine.get_formatted_context("chest pain and tachycardia", condition="Tachycardia")
assert "[CLINICAL GUIDELINE GROUNDING" in ctx
print(f" -> Passed: RAG accurately retrieved '{top_doc['title']}' in {latency_ms:.2f} ms.")
def test_distillation_loss():
print("[TEST 5/7] Testing KnowledgeDistillationLoss...")
criterion = KnowledgeDistillationLoss(alpha=0.5, temperature=2.0)
batch_size = 2
seq_len = 16
vocab_size = 100
student_logits = torch.randn(batch_size, seq_len, vocab_size, requires_grad=True)
teacher_logits = torch.randn(batch_size, seq_len, vocab_size)
labels = torch.randint(0, vocab_size, (batch_size, seq_len))
labels[0, :3] = -100 # Masked prompt tokens
loss = criterion(student_logits, labels, teacher_soft_targets=teacher_logits)
assert not torch.isnan(loss), "Distillation loss returned NaN"
loss.backward()
assert student_logits.grad is not None
print(f" -> Passed: Distillation combined loss calculated: {loss.item():.4f}")
def test_cardiology_domain_coverage():
print("[TEST 6/7] Testing domain coverage of expert cardiology rationales...")
categories = set(p["category"] for p in CardiologyDomainExpert.EXPERT_PROMPTS)
expected = {"Medications", "Nutrition", "Symptoms", "Recovery"}
assert expected.issubset(categories), f"Missing categories: {expected - categories}"
print(f" -> Passed: Complete coverage across {len(CardiologyDomainExpert.EXPERT_PROMPTS)} expert clinical cases.")
def test_safetensors_export_and_budget():
print("[TEST 7/7] Testing safetensors serialization and < 512 MB mobile budget...")
class MockConfig:
hidden_size = 896
vocab_size = 151936
class MockLM(nn.Module):
def __init__(self):
super().__init__()
self.config = MockConfig()
self.embed = nn.Embedding(151936, 896)
self.linear = nn.Linear(896, 896)
def get_input_embeddings(self):
return self.embed
def forward(self, inputs_embeds=None, attention_mask=None, labels=None):
class Out:
pass
o = Out()
o.logits = self.linear(inputs_embeds)
o.loss = torch.tensor(1.23)
return o
mock_student = MockLM()
model = MedGemmaMicroModel(
student_lm=mock_student,
encoder_type="conformer",
projector_type="cross_attention",
)
with tempfile.NamedTemporaryFile(suffix=".safetensors", delete=False) as f:
tmp_path = f.name
try:
size_mb = export_and_verify_checkpoint(model, output_path=tmp_path, target_dtype=torch.float16, budget_limit_mb=512.0)
assert size_mb < 512.0, f"Export exceeded 512 MB: {size_mb} MB"
print(f" -> Passed: Unified mobile model serialized at {size_mb:.2f} MB (< 512 MB ceiling).")
finally:
if os.path.exists(tmp_path):
os.remove(tmp_path)
def run_all_tests():
print("=" * 70)
print("Running MedGemma-Micro Mobile (Sub-512MB) Unit Test Suite")
print("=" * 70)
test_ppg_simulator()
test_conformer_encoder()
test_cross_attention_projector()
test_clinical_rag()
test_distillation_loss()
test_cardiology_domain_coverage()
test_safetensors_export_and_budget()
print("=" * 70)
print("ALL TESTS PASSED SUCCESSFULLY!")
print("=" * 70)
if __name__ == "__main__":
run_all_tests()
|