Ctrl K
ComunidadGuías…

plans/lite-model-plans/04-phase4-final-validation.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Phase 4: Final Validation & Deployment

**Duration**: 1 day
**Risk Level**: Low (validation and polish only)
**Dependencies**: Phases 1, 2, 3 complete

## Objectives

1. Run comprehensive test suite (unit + integration + E2E)
2. Perform memory benchmarking on all quantized models
3. Quality validation on representative memes
4. Docker build verification (AMD64 + ARM64)
5. Create release checklist
6. Final documentation review

## Comprehensive Testing

### Step 4.1: Python Unit Tests

```bash
cd meme_search/image_to_text_generator

# Run full unit test suite
mise exec -- pytest tests/unit/test_model_init.py -v

# Expected: 112 tests (88 original + 24 new)
# - 8 for SmolVLM-256M-int8
# - 8 for SmolVLM-500M-int8
# - 8 for Florence-2-base-int4
# - 8 for Florence-2-large-int4

# Run with coverage
mise exec -- pytest tests/unit/ --cov=app --cov-report=html --cov-report=term

# Verify coverage ≥80%
open htmlcov/index.html
```

**Expected Results:**
- ✅ 112/112 tests passing
- ✅ Coverage ≥80% maintained
- ✅ No warnings or deprecations

### Step 4.2: Python Integration Tests

```bash
# Run integration tests (uses "test" model, fast)
mise exec -- pytest tests/integration/test_app.py -v

# Expected: All tests pass unchanged (no modifications needed)
```

**Expected Results:**
- ✅ All integration tests passing
- ✅ FastAPI endpoints working
- ✅ Rails callback mocking functional

### Step 4.3: Rails Tests

```bash
cd meme_search/meme_search_app

# Reset database with new seeds
mise exec -- bin/rails db:reset RAILS_ENV=test

# Verify 10 models seeded
mise exec -- bin/rails runner "puts ImageToText.count" RAILS_ENV=test
# Expected output: 10

# Run controller tests
mise exec -- bin/rails test test/controllers/settings/image_to_texts_controller_test.rb

# Run all Rails tests
mise exec -- bash run_tests.sh
```

**Expected Results:**
- ✅ 10 ImageToText records in test DB
- ✅ All controller tests passing
- ✅ No regressions in model tests

### Step 4.4: E2E Tests (Playwright)

```bash
# From project root
npm run test:e2e -- image-to-texts.spec.ts

# Or run in UI mode for debugging
npm run test:e2e:ui -- image-to-texts.spec.ts
```

**Expected Results:**
- ✅ Model selection tests pass with 10 models
- ✅ Exclusive selection behavior works
- ✅ UI renders all badges correctly

### Step 4.5: Full E2E Suite (Optional but Recommended)

```bash
# Run all E2E tests (16 tests, ~5-10 min)
npm run test:e2e

# Expected: 16/16 passing (same as before, no regressions)
```

## Memory Benchmarking

### Step 4.6: Create Benchmarking Script

**File**: `meme_search/image_to_text_generator/benchmark_quantized_models.py`

```python
"""
Benchmark memory usage for all quantized models.
Compares base vs quantized memory footprint.
"""
import psutil
import time
from app.model_init import model_selector

def measure_memory(model_name):
    """Measure memory usage for a model"""
    process = psutil.Process()

    # Baseline
    baseline = process.memory_info().rss / 1024 / 1024

    # Load model
    print(f"\nLoading {model_name}...")
    start_time = time.time()
    model = model_selector(model_name)
    model.download()
    load_time = time.time() - start_time

    # After load
    after_load = process.memory_info().rss / 1024 / 1024
    model_memory = after_load - baseline

    return {
        "model": model_name,
        "memory_mb": round(model_memory, 2),
        "load_time_sec": round(load_time, 2)
    }

if __name__ == "__main__":
    models = [
        # SmolVLM family
        ("SmolVLM-256M-Instruct", "SmolVLM-256M-Instruct-int8"),
        ("SmolVLM-500M-Instruct", "SmolVLM-500M-Instruct-int8"),

        # Florence family (skip for now - too slow to download both)
        # ("Florence-2-base", "Florence-2-base-int4"),
        # ("Florence-2-large", "Florence-2-large-int4"),

        # Moondream family
        ("moondream2", "moondream2-int8"),
    ]

    results = []

    for base_model, quant_model in models:
        print(f"\n{'='*60}")
        print(f"Benchmarking {base_model} vs {quant_model}")
        print('='*60)

        base_result = measure_memory(base_model)
        quant_result = measure_memory(quant_model)

        reduction = ((base_result["memory_mb"] - quant_result["memory_mb"]) /
                     base_result["memory_mb"] * 100)

        results.append({
            "family": base_model.split("-")[0],
            "base_model": base_model,
            "base_memory_mb": base_result["memory_mb"],
            "quant_model": quant_model,
            "quant_memory_mb": quant_result["memory_mb"],
            "reduction_percent": round(reduction, 1),
            "base_load_time": base_result["load_time_sec"],
            "quant_load_time": quant_result["load_time_sec"]
        })

    # Print summary table
    print("\n" + "="*80)
    print("MEMORY BENCHMARK SUMMARY")
    print("="*80)
    print(f"{'Model':<30} {'Base Memory':<15} {'Quant Memory':<15} {'Reduction':<12}")
    print("-"*80)

    for r in results:
        print(f"{r['base_model']:<30} {r['base_memory_mb']:>10.2f} MB  "
              f"{r['quant_memory_mb']:>10.2f} MB  {r['reduction_percent']:>8.1f}%")

    print("="*80)

    # Verify expectations
    print("\nVERIFICATION:")
    for r in results:
        expected = {
            "SmolVLM-256M": (256, 500, 40, 60),  # Expect ~256MB, target 49%
            "SmolVLM-500M": (400, 600, 65, 80),  # Expect ~500MB, target 72%
            "moondream2": (1400, 2200, 50, 70),  # Expect ~1.5-2GB, target 60%
        }

        family = r["family"]
        if family in expected:
            min_mem, max_mem, min_reduction, max_reduction = expected[family]
            quant_mem = r["quant_memory_mb"]
            reduction = r["reduction_percent"]

            mem_ok = min_mem <= quant_mem <= max_mem
            reduction_ok = min_reduction <= reduction <= max_reduction

            status = "✅ PASS" if (mem_ok and reduction_ok) else "❌ FAIL"
            print(f"{r['quant_model']:<30} {status}")
            if not mem_ok:
                print(f"  ⚠️ Memory {quant_mem:.2f}MB not in expected range {min_mem}-{max_mem}MB")
            if not reduction_ok:
                print(f"  ⚠️ Reduction {reduction:.1f}% not in expected range {min_reduction}-{max_reduction}%")
```

**Run benchmark**:
```bash
cd meme_search/image_to_text_generator
mise exec -- python benchmark_quantized_models.py
```

**Expected Output**:
```
MEMORY BENCHMARK SUMMARY
================================================================================
Model                          Base Memory     Quant Memory    Reduction
--------------------------------------------------------------------------------
SmolVLM-256M-Instruct               500.00 MB      256.00 MB      48.0%
SmolVLM-500M-Instruct              1800.00 MB      500.00 MB      72.2%
moondream2                         5000.00 MB     1600.00 MB      68.0%
================================================================================

VERIFICATION:
SmolVLM-256M-Instruct-int8     ✅ PASS
SmolVLM-500M-Instruct-int8     ✅ PASS
moondream2-int8                ✅ PASS
```

## Quality Validation

### Step 4.7: Caption Comparison Script

**File**: `meme_search/image_to_text_generator/compare_model_quality.py`

```python
"""
Compare captions between base and quantized models.
Validates quality retention.
"""
from app.model_init import model_selector
import glob

def compare_models(base_model, quant_model, test_images):
    """Compare captions from two models"""
    print(f"\n{'='*80}")
    print(f"Comparing {base_model} vs {quant_model}")
    print('='*80)

    base = model_selector(base_model)
    quant = model_selector(quant_model)

    exact_matches = 0
    total = len(test_images)

    for img_path in test_images:
        base_caption = base.extract(img_path)
        quant_caption = quant.extract(img_path)

        match = base_caption.strip() == quant_caption.strip()
        if match:
            exact_matches += 1

        print(f"\nImage: {img_path.split('/')[-1]}")
        print(f"Base:  {base_caption}")
        print(f"Quant: {quant_caption}")
        print(f"Match: {'✅' if match else '❌'}")

    match_rate = (exact_matches / total) * 100
    print(f"\n{'='*80}")
    print(f"Exact match rate: {exact_matches}/{total} ({match_rate:.1f}%)")
    print('='*80)

    return match_rate

if __name__ == "__main__":
    # Test images
    test_images = glob.glob("public/memes/test/*.jpg") + glob.glob("public/memes/test/*.gif")
    test_images = test_images[:4]  # Limit to 4 for speed

    print(f"Testing on {len(test_images)} images: {[p.split('/')[-1] for p in test_images]}")

    # Compare each family
    comparisons = [
        ("SmolVLM-256M-Instruct", "SmolVLM-256M-Instruct-int8", 90),  # Expect ≥90%
        ("SmolVLM-500M-Instruct", "SmolVLM-500M-Instruct-int8", 90),  # Expect ≥90%
        ("moondream2", "moondream2-int8", 85),  # Expect ≥85%
        # Florence: Skip for now (INT4 is experimental)
    ]

    results = []
    for base, quant, threshold in comparisons:
        match_rate = compare_models(base, quant, test_images)
        passed = match_rate >= threshold
        results.append((quant, match_rate, threshold, passed))

    # Summary
    print("\n" + "="*80)
    print("QUALITY VALIDATION SUMMARY")
    print("="*80)
    for model, rate, threshold, passed in results:
        status = "✅ PASS" if passed else "❌ FAIL"
        print(f"{model:<35} {rate:>5.1f}% (threshold: ≥{threshold}%) {status}")
```

**Run quality check**:
```bash
mise exec -- python compare_model_quality.py
```

**Expected Results:**
- ✅ SmolVLM-256M-int8: ≥90% match rate (proven in docs)
- ✅ SmolVLM-500M-int8: ≥90% match rate (proven in docs)
- ✅ Moondream2-int8: ≥85% match rate (known from existing tests)
- ⚠️ Florence-2-int4: Manual testing only (experimental)

## Docker Build Verification

### Step 4.8: Docker Build Test

```bash
# From project root
cd /Users/neonwatty/Desktop/meme-search

# Build image_to_text_generator service
docker compose -f docker-compose-local-build.yml build image_to_text_generator

# Verify no errors
docker images | grep image_to_text_generator

# Test container starts
docker compose -f docker-compose-local-build.yml up -d image_to_text_generator

# Check logs
docker compose -f docker-compose-local-build.yml logs image_to_text_generator

# Verify service is healthy
curl http://localhost:8000/check_queue

# Clean up
docker compose -f docker-compose-local-build.yml down
```

**Expected Results:**
- ✅ Docker build succeeds with no errors
- ✅ Container starts without import errors for bitsandbytes
- ✅ `/check_queue` endpoint returns 200 OK
- ✅ Image size reasonable (<5GB)

### Step 4.9: ARM64 Compatibility Check (Optional)

If running on Apple Silicon:
```bash
# Build for ARM64
docker compose -f docker-compose-local-build.yml build --platform linux/arm64 image_to_text_generator

# Verify bitsandbytes works on ARM64
docker run --rm --platform linux/arm64 \
  image_to_text_generator \
  python -c "import bitsandbytes; print('BitsAndBytes OK')"
```

**Expected**: "BitsAndBytes OK" (or document as x86-only)

## Release Checklist

### Step 4.10: Pre-Release Checklist

**Code Quality:**
- [ ] All 112 unit tests passing
- [ ] Integration tests passing
- [ ] E2E tests passing (16/16)
- [ ] No linting errors (`mise exec -- ruff check app/`)
- [ ] Coverage ≥80%

**Functionality:**
- [ ] All 10 models selectable in UI
- [ ] Memory savings verified (≥45% for all quantized)
- [ ] Quality validation passed (≥85% for all)
- [ ] Docker build succeeds
- [ ] FastAPI endpoints working

**Documentation:**
- [ ] README.md updated with memory table
- [ ] CLAUDE.md updated with quantization patterns
- [ ] All model descriptions in seeds.rb accurate
- [ ] Hardware recommendations clear
- [ ] Experimental flags on INT4 models

**User Experience:**
- [ ] UI badges displaying correctly
- [ ] Help section explains quantization
- [ ] Model selection works smoothly
- [ ] No JavaScript errors in console
- [ ] Mobile responsive (test on small screen)

### Step 4.11: Known Issues Documentation

**File**: `plans/lite-model-plans/KNOWN_ISSUES.md`

```markdown
# Known Issues - Quantized Models

## Florence-2 INT4 (Experimental)

**Status**: Experimental - validate quality before production use

**Known Limitations:**
- Encoder sensitivity may cause 5-10% quality degradation
- Recommended by Microsoft but requires testing per use case
- Multi-GPU may produce gibberish (single-GPU only)
- No CPU offloading support

**Recommendation**: Test thoroughly on your meme dataset before deploying

## BitsAndBytes Hardware Support

**Supported:**
- NVIDIA GPUs (CUDA)
- AMD GPUs (ROCm)
- CPU (INT4 only, INT8 requires GPU)

**Not Supported:**
- Apple Metal (MPS) - BitsAndBytes doesn't support MPS yet
- Integrated GPUs (may work but slow)

**Workaround**: Use FP16/BF16 models on unsupported hardware

## ARM64 Compatibility

**Status**: Tested on x86-64 primarily

**Notes:**
- Docker builds for ARM64 may work but untested in CI
- BitsAndBytes support for ARM64 varies by version
- M1/M2 Macs: Use MPS-compatible FP16 models for now

## Memory Estimates

Actual memory usage may vary by:
- Hardware (GPU VRAM vs system RAM)
- Batch size
- Concurrent requests
- Other running services

Estimates are for single-image inference.
```

## Final Validation

### Step 4.12: Smoke Test

```bash
# Start full stack locally
./scripts/reset-and-rebuild.sh --keep-models

# Navigate to http://localhost:3000
# 1. Go to Settings > AI Models
# 2. Select SmolVLM-256M-Instruct-int8
# 3. Click "Save Selection"
# 4. Go to Meme Gallery
# 5. Upload a test image
# 6. Click "Generate Descriptions"
# 7. Verify description appears within 10-30s
# 8. Check quality is reasonable

# Repeat for other quantized models
```

**Expected Results:**
- ✅ All quantized models generate descriptions
- ✅ Quality is acceptable (not gibberish)
- ✅ Performance is reasonable (<60s per image)
- ✅ No errors in Rails logs
- ✅ No errors in Python logs

## Completion Criteria

### All Tests Passing
- [ ] 112/112 Python unit tests ✅
- [ ] Integration tests ✅
- [ ] 16/16 E2E tests ✅
- [ ] Rails tests ✅

### Benchmarks Met
- [ ] Memory reduction ≥45% all quantized models ✅
- [ ] Quality retention ≥85% all models ✅
- [ ] Docker build succeeds ✅

### Documentation Complete
- [ ] README.md memory table ✅
- [ ] CLAUDE.md quantization patterns ✅
- [ ] Known issues documented ✅
- [ ] Hardware recommendations clear ✅

### Release Ready
- [ ] Feature branch created ✅
- [ ] All code committed ✅
- [ ] Tests passing in CI (if applicable) ✅
- [ ] Ready for PR review ✅

## Post-Completion

After Phase 4 validation:

1. **Create Pull Request**:
   - Branch: `feature/add-quantized-models` → `main`
   - Title: "Add INT8/INT4 quantized model variants for memory efficiency"
   - Description: Link to plans, include memory benchmarks

2. **Merge to Main** (after review)

3. **Monitor Production**:
   - Watch for user reports on quality
   - Monitor memory usage
   - Collect feedback on model selection

4. **Future Enhancements**:
   - Grouped accordion UI (from Phase 3 Option B)
   - More model variants (BLIP, GIT, Qwen2-VL)
   - ONNX export for CPU optimization
   - Auto-recommendation based on system memory

## Success Metrics

**Quantitative:**
- 10 models available (up from 6) ✅
- ≥45% memory reduction for all quantized ✅
- ≥85% quality retention ✅
- 112 tests (up from 88) ✅

**Qualitative:**
- Users can choose models based on hardware ✅
- Clear documentation and recommendations ✅
- No breaking changes for existing users ✅
- Experimental flags prevent confusion ✅

**This completes the quantized models implementation!** 🎉