# Phase 3: Rails UI Enhancement & Documentation
**Duration**: 2 days
**Risk Level**: Low (UI/documentation only, no model changes)
**Dependencies**: Phase 1 & 2 complete (4 new quantized models added)
## Objectives
1. Enhance Settings UI to clearly explain quantization options
2. Add user education about INT4 vs INT8 trade-offs
3. Update documentation (README, CLAUDE.md)
4. Create E2E tests for new models
5. Verify full integration
## Current State
After Phase 1 & 2, we have:
- **10 total models** (up from 6):
- Florence-2-base (FP16)
- Florence-2-base-int4 (NEW)
- Florence-2-large (FP16)
- Florence-2-large-int4 (NEW)
- SmolVLM-256M-Instruct (BF16)
- SmolVLM-256M-Instruct-int8 (NEW)
- SmolVLM-500M-Instruct (BF16)
- SmolVLM-500M-Instruct-int8 (NEW)
- Moondream2 (FP16)
- Moondream2-int8 (EXISTING)
Current UI: Flat list of 10 cards (cluttered)
## UI Enhancement Options
### Option A: Add Badges & Memory Info (Minimal Change)
Keep current card layout but enhance each card with:
- **Memory badge**: `💾 ~256MB` or `💾 ~1.5GB`
- **Quantization badge**: `[INT4 Quantized]` or `[INT8 Optimized]`
- **Recommendation badge**: `[💚 CPU-Friendly]` or `[⚡ GPU Recommended]`
**Pros**: Simple, low risk, quick to implement
**Cons**: Still 10 separate cards, not grouped
### Option B: Grouped Accordion UI (Optimal, More Work)
Group models by family with expandable quantization options:
- Florence-2-base ▼
- Full Precision (FP16) - ~460MB
- INT4 Optimized - ~115MB [💚 Recommended for <512MB]
- SmolVLM-256M ▼
- Full Precision (BF16) - ~500MB
- INT8 Optimized - ~256MB [💚 Recommended for CPU]
**Pros**: Clean, scalable, educational
**Cons**: More complex, requires Stimulus controller changes
### Recommendation: **Option A for Phase 3**
Implement Option A (badges) now for quick wins. Option B (accordion) can be a future enhancement when we have more model variants.
## Implementation Steps
### Step 3.1: Update Seeds with Enhanced Descriptions
**File**: `meme_search/meme_search_app/db/seeds.rb`
**Enhance descriptions** to include hardware recommendations:
```ruby
descriptions = [
# Florence-2-base
'Microsoft Florence-2-base (250M params, ~460MB). Balanced performance for general meme captioning. Best for: GPU with 1GB+ VRAM or modern CPU.',
# Florence-2-base-int4
'INT4 quantized Florence-2-base (~115MB, 75% reduction). Ultra-compact using NormalFloat4 quantization. Slight quality trade-off (5-10%). Best for: CPU-only or <512MB memory. [Experimental - validate quality]',
# Florence-2-large
'Microsoft Florence-2-large (700M params, ~1.54GB). Best quality in Florence series. Best for: GPU with 2GB+ VRAM.',
# Florence-2-large-int4
'INT4 quantized Florence-2-large (~385MB, 75% reduction). Best quality under 500MB. Slight quality trade-off (5-10%). Best for: Limited GPU memory or CPU with 1GB+ RAM. [Experimental - validate quality]',
# SmolVLM-256M
'Hugging Face SmolVLM-256M (256M params, ~500MB). Compact modern VLM. Best for: Balanced CPU/GPU deployment.',
# SmolVLM-256M-int8
'INT8 quantized SmolVLM-256M (~256MB, 49% reduction). Proven 1.7x faster TTFT, minimal quality loss. Best for: CPU-only machines, laptops, edge devices.',
# SmolVLM-500M
'Hugging Face SmolVLM-500M (500M params, ~1.8GB). Enhanced quality over 256M. Best for: GPU or high-memory CPU.',
# SmolVLM-500M-int8
'INT8 quantized SmolVLM-500M (~500MB, 72% reduction). Best CPU option for quality. Proven 1.4x throughput boost, minimal quality loss. Best for: CPU deployment with quality requirements.',
# Moondream2
'Moondream2 (1.9B params, ~5GB). High-quality meme understanding, excellent OCR. Best for: GPU with 6GB+ VRAM.',
# Moondream2-int8
'INT8 quantized Moondream2 (~1.5-2GB, 60% reduction). Best quality for memory-constrained setups. Minimal quality loss (0-5%). Best for: CPU-only or GPU with 2-4GB VRAM.'
]
```
### Step 3.2: Add UI Helper for Memory Badges
**File**: `meme_search/meme_search_app/app/helpers/settings/image_to_texts_helper.rb` (create if doesn't exist)
```ruby
module Settings
module ImageToTextsHelper
# Returns memory estimate for a given model
def model_memory_estimate(model_name)
memory_map = {
"Florence-2-base" => "~460MB",
"Florence-2-base-int4" => "~115MB",
"Florence-2-large" => "~1.5GB",
"Florence-2-large-int4" => "~385MB",
"SmolVLM-256M-Instruct" => "~500MB",
"SmolVLM-256M-Instruct-int8" => "~256MB",
"SmolVLM-500M-Instruct" => "~1.8GB",
"SmolVLM-500M-Instruct-int8" => "~500MB",
"moondream2" => "~5GB",
"moondream2-int8" => "~1.5GB"
}
memory_map[model_name] || "Unknown"
end
# Returns badge color class for quantized models
def quantization_badge_class(model_name)
if model_name.include?("int4")
"bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200"
elsif model_name.include?("int8")
"bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
else
"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
end
end
# Returns quantization type label
def quantization_label(model_name)
if model_name.include?("int4")
"INT4 Optimized"
elsif model_name.include?("int8")
"INT8 Optimized"
else
"Full Precision"
end
end
end
end
```
### Step 3.3: Update View Template
**File**: `meme_search/meme_search_app/app/views/settings/image_to_texts/index.html.erb`
**Enhance model card** (around line 17-64):
```erb
<% @image_to_texts.each do |image_to_text| %>
<%= image_to_text.name %>
💾 <%= model_memory_estimate(image_to_text.name) %>
<%= quantization_label(image_to_text.name) %>
<% if image_to_text.name.include?("int4") %>
⚠️ Experimental
<% end %>
<% if image_to_text.current %>
Active
<% end %>
<%= image_to_text.description %>
<% end %>
```
### Step 3.4: Add Help Section
**File**: `meme_search/meme_search_app/app/views/settings/image_to_texts/index.html.erb`
**Add before model cards** (after line 10):
```erb
Understanding Model Options
Full Precision (FP16/BF16): Original model weights, highest quality, more memory.
INT8 Optimized: 8-bit quantization, ~50% memory reduction, minimal quality loss (0-5%). Proven for SmolVLM and Moondream.
INT4 Optimized: 4-bit quantization, ~75% memory reduction, slight quality loss (5-10%). Recommended by Microsoft for Florence models. Validate quality for your use case.
```
### Step 3.5: Update E2E Tests
**File**: `playwright/tests/image-to-texts.spec.ts`
**Update model count** (currently expects 6, now 10):
```typescript
test('displays the image to text settings heading', async ({ page }) => {
await imageToTextsPage.goto();
expect(await imageToTextsPage.heading()).toBe('AI Models');
// Verify 10 models are displayed (was 6)
const modelCards = await page.locator('.rounded-2xl').count();
expect(modelCards).toBe(10);
});
test('updating the current model to all available models', async ({ page }) => {
await imageToTextsPage.goto();
// Update model IDs to include new quantized variants
// Assuming IDs 1-10 (Florence-2-base is 1, new models get sequential IDs)
const modelIds = [2, 3, 4, 5, 6, 7, 8, 9, 10, 1]; // Test all, end with default
const allIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
for (const modelId of modelIds) {
await imageToTextsPage.selectModel(modelId);
await imageToTextsPage.saveSelection();
expect(await imageToTextsPage.isModelSelected(modelId)).toBe(true);
// Verify exclusive selection (only one active)
for (const otherId of allIds) {
if (otherId !== modelId) {
expect(await imageToTextsPage.isModelSelected(otherId)).toBe(false);
}
}
}
});
```
### Step 3.6: Update README.md
**File**: `README.md`
**Add memory comparison table** (in Models section):
```markdown
## AI Models
Meme Search supports multiple vision-language models with different memory requirements:
### Memory Comparison
| Model | Parameters | Precision | Memory | Quality | Best For |
|-------|-----------|-----------|--------|---------|----------|
| Florence-2-base | 250M | FP16 | ~460MB | Baseline | GPU, balanced |
| Florence-2-base | 250M | INT4 | **~115MB** | 90-95% | Ultra-low memory |
| Florence-2-large | 700M | FP16 | ~1.5GB | High | GPU, quality |
| Florence-2-large | 700M | INT4 | **~385MB** | 90-95% | Limited GPU |
| SmolVLM-256M | 256M | BF16 | ~500MB | Baseline | Compact |
| SmolVLM-256M | 256M | INT8 | **~256MB** | 99%+ | CPU/laptop |
| SmolVLM-500M | 500M | BF16 | ~1.8GB | Enhanced | GPU/high-mem CPU |
| SmolVLM-500M | 500M | INT8 | **~500MB** | 99%+ | CPU quality |
| Moondream2 | 1.9B | FP16 | ~5GB | Highest | GPU 6GB+ |
| Moondream2 | 1.9B | INT8 | **~1.5GB** | 95-100% | CPU/limited GPU |
### Quantization Methods
- **INT8**: 8-bit integer quantization, ~50% memory reduction, minimal quality loss (0-5%)
- **INT4**: 4-bit integer quantization, ~75% memory reduction, slight quality loss (5-10%)
### Hardware Recommendations
**<512MB available:**
- Florence-2-base-int4 (~115MB) ⭐ Recommended
**512MB-1GB available:**
- SmolVLM-256M-int8 (~256MB)
- Florence-2-large-int4 (~385MB)
**1-2GB available:**
- SmolVLM-500M-int8 (~500MB)
- Moondream2-int8 (~1.5GB) ⭐ Best quality in this range
**2-4GB available:**
- Florence-2-large (~1.5GB)
- Moondream2-int8 (~1.5GB)
**4GB+ available:**
- Moondream2 (~5GB) ⭐ Best overall quality
```
### Step 3.7: Update CLAUDE.md
**File**: `CLAUDE.md`
**Add quantization patterns section**:
```markdown
## Quantization Patterns
### Adding Quantized Model Variants
Follow the established pattern from `MoondreamQuantizedImageToText`:
**INT8 Pattern** (for most models):
```python
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_8bit=True,
llm_int8_threshold=6.0,
)
model = AutoModelForCausalLM.from_pretrained(
"model/name",
quantization_config=quantization_config,
device_map="auto", # Required - don't use .to(device)
trust_remote_code=True
)
```
**INT4 Pattern** (for Florence-2 or aggressive compression):
```python
import torch
from transformers import BitsAndBytesConfig
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
bnb_4bit_quant_type="nf4"
)
model = AutoModelForCausalLM.from_pretrained(
"model/name",
quantization_config=quantization_config,
device_map="auto",
trust_remote_code=True
)
```
**Key Constraints:**
- Cannot call `.to(device)` after loading with quantization
- Must use `device_map="auto"` - BitsAndBytes manages device placement
- No torch_dtype parameter needed - quantization config handles it
- GPU-only (NVIDIA/AMD, no MPS support)
```
## Testing Checklist
### Rails Tests
- [ ] Update model count in seeds: 6 → 10 models
- [ ] Run `mise exec -- bin/rails db:seed:replant` (test environment)
- [ ] Verify 10 ImageToText records created
- [ ] Check descriptions include hardware recommendations
### E2E Tests
- [ ] Update `image-to-texts.spec.ts` for 10 models
- [ ] Run `npm run test:e2e -- image-to-texts.spec.ts`
- [ ] Verify all model selection tests pass
- [ ] Check new badges are visible in UI
### UI Manual Testing
- [ ] Navigate to Settings > AI Models
- [ ] Verify 10 models displayed with badges
- [ ] Check memory estimates are correct
- [ ] Verify INT4 models show "Experimental" badge
- [ ] Verify INT8 models show "INT8 Optimized" badge
- [ ] Check help section displays correctly
- [ ] Test model selection and save
- [ ] Verify dark mode styling
### Documentation
- [ ] README.md updated with memory table
- [ ] CLAUDE.md updated with quantization patterns
- [ ] Hardware recommendations are clear
- [ ] Links work correctly
## Expected Outcomes
### UI Improvements
- ✅ Clear memory information on each model card
- ✅ Visual distinction between FP16/INT8/INT4 variants
- ✅ User education section explaining quantization
- ✅ Experimental warnings for INT4 models
### Documentation
- ✅ Complete memory comparison table in README
- ✅ Hardware recommendations for different RAM constraints
- ✅ Code patterns documented in CLAUDE.md
- ✅ Clear explanation of INT4 vs INT8 trade-offs
### Tests
- ✅ E2E tests updated for 10 models
- ✅ All tests passing
- ✅ UI renders correctly in browser testing
## Completion Criteria
- [ ] Rails helper methods implemented
- [ ] View template enhanced with badges
- [ ] Help section added to UI
- [ ] E2E tests updated and passing
- [ ] README.md memory table added
- [ ] CLAUDE.md quantization patterns documented
- [ ] Manual UI testing complete
- [ ] All 10 models selectable and functional
## Next Phase
After Phase 3 completion, proceed to **Phase 4: Final Validation & Deployment** (final testing, Docker builds, release preparation).