googleapis / googleapis/python-genai

Inconsistent Probability Scores Between GCS URI and Image Bytes Input

Open
#2,154 1 comment 0 reactions 1 assignee Claimed by @Venkaiahbabuneelam View on GitHub
priority: p2 type: bug
Dominant language
Python
Stars
4k
Forks
1k
Avg merge
2d 11h
Merged PRs (30d)
40

Description

#### Environment details

- Programming language: Python
- OS: MacOS
- Language runtime version: Python 3.12.9
- Package version: 1.67.0

#### Steps to reproduce

# Bug Report: Inconsistent Probability Scores Between GCS URI and Image Bytes Input for fine tuned model (Flash 2.5 Lite)

## Summary

The Gemini API (via `google.genai` library) returns **significantly different probability scores** (avg_logprobs) for identical images depending on whether the image is provided as a GCS URI or as raw image bytes, even when using identical configuration parameters.

## Environment

- **Library**: `google-genai` with Vertex AI
- **Model**: Fine-tuned Gemini model on Vertex AI
- **Client**: `genai.Client(vertexai=True)`
- **Project**: `hidden`
- **Location**: `us-east4`
- **API Version**: `v1`
- **Language**: Python

## Reproduction Steps

### Setup Code

```python
from google import genai
from google.genai.types import HttpOptions
from google.genai import types
from google.cloud import storage
import math

# Initialize clients
client = genai.Client(
vertexai=True,
project="account-name",
location="us-east4",
http_options=HttpOptions(api_version="v1")
)
storage_client = storage.Client(project="account-name")

# Configuration (identical for both methods)
config = {
"temperature": 0,
"top_p": 1.0,
"top_k": 1,
"candidate_count": 1,
"response_logprobs": True,
"seed": 42,
}

model_name = "projects/{account_id}/locations/us-east4/endpoints/{endpoint}"
instructions = "Your instruction text here"
```

### Method 1: GCS URI (Direct Reference)

```python
gcs_uri = "gs://{bucket}/{path-to-image}.jpg"

response1 = client.models.generate_content(
model=model_name,
contents=[gcs_uri, instructions],
config=config
)

prediction1 = response1.text.strip()
probability1 = math.exp(response1.candidates[0].avg_logprobs)
```

### Method 2: Image Bytes (Downloaded First)

```python
# Parse and download from GCS
uri_parts = gcs_uri[5:].split("/", 1)
bucket_name = uri_parts[0]
blob_path = uri_parts[1]

bucket = storage_client.bucket(bucket_name)
blob = bucket.blob(blob_path)
image_data = blob.download_as_bytes()

# Create image part from bytes
image = types.Part.from_bytes(data=image_data, mime_type="image/jpeg")

response2 = client.models.generate_content(
model=model_name,
contents=[image, instructions],
config=config
)

prediction2 = response2.text.strip()
probability2 = math.exp(response2.candidates[0].avg_logprobs)
```

## Expected Behavior

Both methods should return **identical** or **nearly identical** probability scores since:
- ✅ Same image content (byte-for-byte identical - **SHA-256 hash verified**)
- ✅ Same model
- ✅ Same configuration parameters
- ✅ Same instructions
- ✅ Deterministic settings (`temperature=0`, `seed=42`)

**Image Identity Verification:**
```python
import hashlib

# Download image and compute hash
image_bytes = blob.download_as_bytes()
sha256_hash = hashlib.sha256(image_bytes).hexdigest()
# Hash: [verified in test notebook]
# File size: [verified in test notebook]
```

Both methods use the exact same GCS object - the downloaded bytes in Method 2 are **proven identical** to the image referenced by the URI in Method 1.

## Actual Behavior

**Dramatically different probability scores:**

| Method | Input Type | Prediction | Probability | Difference |
|--------|-----------|------------|-------------|------------|
| GCS URI | `gs://bucket/path/image.jpg` | `0` | **0.8365** | - |
| Image Bytes | `types.Part.from_bytes()` | `0` | **0.9999** | **+0.163** (19.5% increase) |

### Additional Testing

We verified this finding by ensuring both methods use **identical configuration parameters**:

```python
# Same config for both methods
config = {
"temperature": 0,
"top_p": 1.0,
"top_k": 1,
"candidate_count": 1,
"response_logprobs": True,
"seed": 42,
}
```

| Method | Input Type | Config | Probability |
|--------|-----------|--------|-------------|
| 1 | GCS URI | Identical | **0.8365** |
| 2 | Image Bytes | Identical | **0.9999** |

**Key Finding**: The probability difference persists even with identical configuration parameters. The difference is **entirely** due to the image input format.

## Impact

### 1. **Inconsistent Confidence Scores**
Production systems using different input methods will report vastly different confidence levels for identical predictions. A 20% probability difference is significant.

### 2. **Non-Deterministic Behavior**
Despite setting `temperature=0` and `seed=42`, the API produces different probability scores based solely on input format.

### 3. **API Contract Violation**
The same configuration parameters should produce deterministic results regardless of whether the image is provided as a URI or bytes.

### 4. **Evaluation/Production Mismatch**
- Models evaluated using GCS URIs (common in batch processing) show different probability distributions than production systems using image bytes
- This breaks the fundamental assumption that evaluation metrics reflect production performance

### 5. **Real-World Consequences**
- Confidence-based thresholds behave differently in evaluation vs. production
- Monitoring systems may incorrectly flag drift or anomalies
- Decision-making systems relying on probability scores may behave unpredictably

## Hypothesis

The Gemini API likely uses different internal code paths for processing:
- **GCS URIs**: May apply additional preprocessing, caching, or use a different inference pipeline
- **Image Bytes**: Direct inference path with different probability calculation

This results in different `avg_logprobs` values even with identical model, images, and configuration.

## Additional Notes

- ✅ **Image content verified identical**: SHA-256 hash confirms both methods process the exact same bytes
- ✅ **Predictions remain identical** (both output `0`) - only probability scores differ
- ✅ **Reproducible** across multiple test images
- ✅ **Consistent pattern**: GCS URIs always produce lower probability scores (~0.84), bytes always produce higher scores (~0.99)
- ❌ Configuration parameters appear to be **ignored or handled differently** when using GCS URIs

## Workaround

For consistent probability scores, always use the same input method:
- **Option A**: Always download images and send bytes
- **Option B**: Always use GCS URIs (but accept that config params may not work as expected)

We have opted for Option A to ensure configuration parameters are respected.

## Expected Fix

The API should:
1. Return identical (or nearly identical) probability scores for identical inputs regardless of format
2. Respect all configuration parameters (`top_p`, `top_k`, `candidate_count`) consistently for both input methods
3. Document any known differences in behavior between GCS URI and bytes input methods

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.