Showing posts with label cache. Show all posts
Showing posts with label cache. Show all posts

Thursday

Python code examples for implementing an ML ensemble using Redis

 

Important Setup Note: RedisAI and RedisML are no longer actively maintained as of 2025/2026. Therefore, Pattern B has evolved into standard deployment patterns using lightweight modern tools (like ONNX Runtime or FastAPI workers coupled directly with Redis), ensuring your pipeline remains production-grade.

Pattern A: Asynchronous Parallel Processing (Redis Streams)

Best for: Heavy models (Deep Learning, large Random Forests) that need separate workers or hardware (GPUs) to compute in parallel without locking your main API.

Architecture Flow

[Client Request] 
       │
       ▼
 ┌───────────┐       XADD       ┌───────────────┐
 │  FastAPI  │ ────────────────>│ Redis Stream  │──┐ (Model 1 Worker)
 │  Gateway  │                  │(ml:requests)  │──┼─> [Model 2 Worker]
 └───────────┘                  └───────────────┘──┘ (Model 3 Worker)
       │                                                   │
       │ (Polls responses via                              │ XADD
       │  unique Request ID)                               ▼
       │                        ┌───────────────┐  ┌───────────────┐
       └────────────────────────│ Redis Hash    │<─│ Ensemble      │
                                │(ml:results:ID)│  │ Aggregator    │
                                └───────────────┘  └───────────────┘

Python Implementation

You will need redis and scikit-learn for this pattern. Run pip install redis scikit-learn.

Python
import time
import json
import uuid
import threading
import redis
from sklearn.linear_model import LogisticRegression
import numpy as np

# Connect to Redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# 1. API GATEWAY FUNCTION (Pushes tasks, awaits final decision)
def mock_api_gateway(features):
    request_id = str(uuid.uuid4())
    payload = {"request_id": request_id, "features": json.dumps(features)}
    
    # Broadcast to the stream
    r.xadd("ml:requests", payload)
    print(f"[API] Dispatched request {request_id}")
    
    # Poll for the ensemble aggregator's final answer
    result_key = f"ml:results:{request_id}"
    while True:
        final_decision = r.hgetall(result_key)
        if final_decision:
            r.delete(result_key) # Clean up
            return final_decision
        time.sleep(0.01) # 10ms polling backoff

# 2. MODEL WORKERS (Simulating 2 different models processing concurrently)
def run_model_worker(model_name, weight_offset):
    # Dummy local model initialization
    model = LogisticRegression()
    
    while True:
        # Read from stream (simulating listening for real-time events)
        messages = r.xread({"ml:requests": "$"}, block=100)
        if not messages:
            continue
            
        for stream, msgs in messages:
            for msg_id, data in msgs:
                req_id = data["request_id"]
                features = json.loads(data["features"])
                
                # Mock prediction: calculate probability
                raw_prob = float(np.mean(features)) * weight_offset
                prob = min(max(raw_prob, 0.0), 1.0)
                
                # Write individual prediction back to a Redis Hash unique to this request
                r.hset(f"ml:preds:{req_id}", model_name, prob)

# 3. ENSEMBLE AGGREGATOR WORKER
def run_ensemble_aggregator():
    while True:
        # Scan for active prediction hashes
        for key in r.scan_iter("ml:preds:*"):
            req_id = key.split(":")[-1]
            predictions = r.hgetall(key)
            
            # Check if both expected model workers have responded
            if len(predictions) == 2:
                prob1 = float(predictions["model_alpha"])
                prob2 = float(predictions["model_beta"])
                
                # Ensemble Logic: Simple Average
                ensemble_prob = (prob1 + prob2) / 2
                decision = "Approved" if ensemble_prob > 0.5 else "Rejected"
                
                # Publish final outcome back to API
                r.hset(f"ml:results:{req_id}", mapping={
                    "decision": decision,
                    "probability": ensemble_prob
                })
                r.delete(key) # Clear intermediate predictions

# --- TEST EXECUTION LOOP ---
if __name__ == "__main__":
    # Flush old data for clean demo
    r.flushdb()
    
    # Start background threads for workers
    threading.Thread(target=run_model_worker, args=("model_alpha", 1.2), daemon=True).start()
    threading.Thread(target=run_model_worker, args=("model_beta", 0.8), daemon=True).start()
    threading.Thread(target=run_ensemble_aggregator, daemon=True).start()
    
    time.sleep(1) # Allow workers to boot
    
    # Simulate a client checking credit features
    mock_features = [0.4, 0.6, 0.55, 0.7] 
    output = mock_api_gateway(mock_features)
    print(f"[API] Final Ensemble Response: {output}")

Pattern B: Synchronous In-Memory Processing (Pipeline)

Best for: Light-to-medium models (like Scikit-Learn or XGBoost) where raw speed matters most. Instead of threading out across streams, your API queries a centralized Redis backend using a pipeline to get all required features at once, then locally runs predictions.

Architecture Flow

[Client Request] ──> [FastAPI Server]
                         │
                         ├─> Redis Pipeline (MGET Features) 
                         │       │
                         │       ▼
                         │   [Redis In-Memory Key Store]
                         │       │ (Returns user & context features <1ms)
                         ▼       ▼
                    Executes Model 1, 2, & 3 locally
                         │
                         ▼
                    Ensemble Aggregation ──> [Return Decision]

Python Implementation

Python
import redis
import json
import numpy as np

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

# Mock some feature store data inside Redis
r.hset("features:user_99", mapping={"risk_score": "0.34", "account_age_days": "120"})
r.hset("features:merchant_44", mapping={"fraud_velocity": "0.05", "trust_rating": "0.91"})

def predict_ensemble_sync(user_id, merchant_id):
    # 1. Pipeline Feature Store Retrieval (Single network round-trip)
    pipe = r.pipeline()
    pipe.hgetall(f"features:{user_id}")
    pipe.hgetall(f"features:{merchant_44}")
    user_feats, merchant_feats = pipe.execute()
    
    # Combine feature vectors
    base_features = [
        float(user_feats["risk_score"]),
        float(user_feats["account_age_days"]) / 365.0, # normalized
        float(merchant_feats["fraud_velocity"]),
        float(merchant_feats["trust_rating"])
    ]
    
    # 2. Synchronous Local Ensemble Execution
    # Model 1: Linear rule
    pred_1 = 1.0 if base_features[0] > 0.5 else 0.0
    # Model 2: Statistical weight
    pred_2 = (base_features[3] * 0.7) + (1 - base_features[2]) * 0.3
    
    # 3. Aggregation (Ensemble Rule: Weighted Voting)
    final_score = (pred_1 * 0.4) + (pred_2 * 0.6)
    return "Flagged Fraud" if final_score < 0.5 else "Authorized"

# Test synchronous run
decision = predict_ensemble_sync("user_99", "merchant_44")
print(f"[Sync API] Ensemble Outcome: {decision}")

Pattern C: Caching Decisions (The Shield)

Regardless of whether you use Pattern A or B, you should shield your models from processing repetitive inputs using an exact signature cache lookup.

Python
import hashlib
import redis

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

def get_ensemble_decision_with_cache(user_id, context_payload):
    # Generate a deterministic cache key based on inputs
    payload_string = json.dumps(context_payload, sort_keys=True)
    payload_hash = hashlib.sha256(payload_string.encode()).hexdigest()
    cache_key = f"cache:decision:{user_id}:{payload_hash}"
    
    # 1. Hit the cache layer
    cached_decision = r.get(cache_key)
    if cached_decision:
        print("[Cache] Match found! Zero model calculation needed.")
        return json.loads(cached_decision)
        
    # 2. Cache Miss: Run your actual ensemble pipeline (from Pattern A or B)
    print("[Cache] Miss. Running full ensemble inference...")
    computed_decision = {"status": "Approved", "confidence": 0.89}
    
    # 3. Save to cache with a 60-second Time-To-Live (TTL)
    r.setex(cache_key, 60, json.dumps(computed_decision))
    return computed_decision

# Test caching execution
payload = {"amount": 250.00, "location": "NY"}
print(get_ensemble_decision_with_cache("user_99", payload))
print(get_ensemble_decision_with_cache("user_99", payload)) # Secondary call hits cache

Building a Real-Time Feature Store with Redis This video outlines Redis' architectures, illustrating how it manages massive operational throughput for contextual data architectures in modern live-serving applications.

Saturday

Dynamic Programming (DP) & GPUs KV Caching

 

                                               generated by Gemini AI

Dynamic Programming (DP) is a powerful algorithmic paradigm used to solve complex problems by breaking them down into simpler sub-problems, solving each sub-problem just once, and storing their solutions—usually using memory-based structures like arrays or tables—to avoid redundant computations.

It is highly effective for problems that exhibit two core properties:

  • Overlapping Sub-problems: The problem can be broken down into sub-problems which are reused multiple times.

  • Optimal Substructure: The optimal solution to the global problem can be constructed from the optimal solutions of its sub-problems.

Dynamic Programming (DP), GPUs, and KV caching are deeply intertwined in modern AI workloads—particularly in large language models (LLMs) and sequence-to-sequence architectures.

At a high level, DP is an algorithmic concept (breaking down a problem into sequential sub-problems), while a GPU is hardware optimized for massive parallelism, and a KV cache is a memory optimization technique designed to eliminate redundant sequential recalculations.

Here is how these three components connect and interact:

1. Dynamic Programming vs. GPU Parallelism: The Core Conflict

Dynamic Programming is inherently sequential. Because step t relies on the calculated results of step t-1 (the optimal substructure), classic DP algorithms do not naturally map to the massive parallel processing power of a GPU.

  • The Challenge: A GPU contains thousands of arithmetic cores meant to execute the exact same operation simultaneously across huge blocks of data (SIMD - Single Instruction, Multiple Data). If a DP algorithm forces the system to wait for thread 1 to finish before thread 2 can start, the GPU experiences thread starvation and becomes highly inefficient.

  • The Solution (Parallel DP): To leverage a GPU, DP algorithms must be rewritten to find independent sub-problems within the sequential steps. For example:

    • Sequence Alignment (e.g., Smith-Waterman in bioinformatics): Instead of calculating cell-by-cell, GPUs compute entire anti-diagonals of the DP scoring matrix in parallel, because the cells along a diagonal do not depend on each other.

    • Viterbi/Hidden Markov Models: The GPU calculates the transition probabilities for all possible hidden states at time step t simultaneously before moving to step t+1.

2. KV Caching as a Hardware-Aware Dynamic Programming

In Transformer-based LLMs, text generation is an autoregressive process: to predict token t, the model must look back at tokens 1 through t-1.

The Attention mechanism requires calculating Key (K) and Value (V) matrices for every token in the sequence. If you regenerate these matrices for the entire prompt every single time you generate a new word, you are doing redundant work.

The DP Connection

KV caching is, conceptually, Memoization—the fundamental top-down optimization technique of Dynamic Programming.

  • Sub-problem: Compute the Key and Value representations of the sequence up to length t.

  • Overlapping Sub-problems: To compute token t+1, you need the exact same Key and Value representations of tokens 1 through t that you just calculated in the previous step.

  • The DP Solution (KV Cache): Instead of recomputing the attention matrix from scratch (an O(N^3) computational burden over time), the system stores the K and V tensors of past tokens in memory. At step t+1, the GPU only computes K and V for the single new token and appends it to the cache, dropping the incremental computational cost per token to O(N).

3. The GPU Memory Bottleneck (Why KV Cache is Tricky)

While KV caching elegantly solves the computational redundancy (acting like a classic DP table), it introduces a massive hardware bottleneck on the GPU.

Compute-Bound vs. Memory-Bound

  • Prefill Phase (Processing the prompt): This is compute-bound. The GPU processes all prompt tokens at once in parallel. This utilizes the GPU’s computing cores perfectly.

  • Decoding Phase (Generating tokens one by one): This is memory-bound. Because of the sequential nature of autoregressive generation, the GPU cannot parallelize across time. For every single token generated, the GPU must fetch the entire history of KV caches from its global memory (High Bandwidth Memory, or HBM) to its local caches (SRAM), perform a tiny calculation, and write the new cache back.

The Dynamic Memory Problem: PagedAttention

Just like classic DP matrix sizes change based on the input string length, KV caches grow dynamically with every generated token.

Because LLM generation lengths are unpredictable, engineers historically had to pre-allocate maximum memory blocks on the GPU for each request. This led to massive memory fragmentation (up to 60-80% wasted space).

Modern systems solve this using PagedAttention (pioneered by vLLM). It borrows the concept of Virtual Memory and Paging from operating systems. The dynamic programming "table" (the KV cache) is broken up into fixed-size blocks and scattered non-contiguously across the GPU memory, drastically increasing throughput by allowing the GPU to fully pack its VRAM.

Summary Matrix

ConceptWhat it providesRole in Modern AIGPU Interaction
Dynamic ProgrammingAlgorithmic ParadigmThe mathematical foundation for handling sequential data and optimal state transitions.Hard to parallelize; requires restructuring loops into independent matrix ops.
KV CacheMemoization TableActs as the "DP table" for Transformers, storing past context to eliminate redundant calculations.Relieves the GPU compute cores but heavily taxes GPU memory bandwidth.
GPU ComputationHardware ExecutionExecutes the parallel tensor operations (Matrix Multiplications) required at each step.Thrives on the large matrix ops during prompt processing; slowed down by sequential token generation.

Friday

Azure Session Management

 

Photo by SHVETS production

Say we are going to create an application for customer management. Which requires faster interaction
from the customer to the application. So we need to manage the session with cache. Users can log in
from any device and use it seamlessly from any other device without any issues.




Below is an end-to-end solution for user login and session management using Azure Redis Cache,
API Gateway, Load Balancer, Azure App Service, and Azure Function serverless with a Flask
application in the backend.

In Azure, maintaining distributed session data typically involves using a combination of Azure services
and technologies.
Here are some best practices and technologies you can use to keep and manage distributed session data:

1. Azure Cache for Redis:
   - Description: Azure Cache for Redis is a fully managed, in-memory data store service built on the
popular open-source Redis. It is commonly used to store and manage session data for web applications.

   - Key Features:
     - In-Memory Storage
     - High Throughput
     - Support for Advanced Data Structures
     - Redis Pub/Sub for Messaging

   - Usage Example:
     ```python
     # Using Azure SDK for Python to interact with Azure Cache for Redis
     from azure.identity import DefaultAzureCredential
     from azure.redis.cache import RedisCacheClient

     credential = DefaultAzureCredential()
     redis_cache = RedisCacheClient.from_connection_string('your_connection_string',
credential=credential)

     # Set session data
     redis_cache.set('session_key', 'session_value')

     # Get session data
     session_data = redis_cache.get('session_key')
     ```

2. Azure Cosmos DB:
   - Description: Azure Cosmos DB is a multi-model, globally distributed database service.
It can be used to store and manage session data, offering high availability and low-latency access.

   - Key Features:
     - Multi-Model (Document, Graph, Table, etc.)
     - Global Distribution
     - Automatic and Instantaneous Scaling

   - Usage Example:
     ```python
     # Using Azure SDK for Python to interact with Azure Cosmos DB
     from azure.identity import DefaultAzureCredential
     from azure.cosmos import CosmosClient

     credential = DefaultAzureCredential()
     cosmos_client = CosmosClient('your_cosmos_db_connection_string', credential=credential)

     # Access and manipulate session data using Cosmos DB APIs
     ```

3. Azure SQL Database:
   - Description: Azure SQL Database is a fully managed relational database service. It can be used
to store session data, especially if your application relies on relational data models.

   - Key Features:
     - Fully Managed
     - High Availability
     - Scalability

   - Usage Example:
     ```python
     # Using Azure SDK for Python to interact with Azure SQL Database
     from azure.identity import DefaultAzureCredential
     from azure.sql.database import SqlDatabaseClient

     credential = DefaultAzureCredential()
     sql_db_client = SqlDatabaseClient('your_sql_db_connection_string', credential=credential)

     # Access and manipulate session data using SQL queries
     ```

4. Azure Blob Storage:
   - Description: Azure Blob Storage can be used to store session data in a distributed manner. It's
suitable for scenarios where you need to store large amounts of unstructured data.

   - Key Features:
     - Scalable and Durable
     - Cost-Effective
     - Blob Storage Tiers for Data Lifecycle Management

   - Usage Example:
     ```python
     # Using Azure SDK for Python to interact with Azure Blob Storage
     from azure.identity import DefaultAzureCredential
     from azure.storage.blob import BlobServiceClient

     credential = DefaultAzureCredential()
     blob_service_client = BlobServiceClient('your_blob_storage_connection_string',
credential=credential)

     # Store and retrieve session data as blobs
     ```

5. Azure Functions for Session Management:
   - Description: Azure Functions can be used to handle session-related logic. You can trigger functions
based on events such as user authentication or session expiration.

   - Key Features:
     - Serverless Architecture
     - Event-Driven
     - Scale Automatically

   - Usage Example:
     - Implement Azure Functions that respond to authentication or session-related events and interact
with the chosen data storage solution.

Best Practices:
- Use Secure Connections: Ensure that connections to your chosen data storage solutions are secured
using appropriate protocols and authentication mechanisms.

- Implement Session Expiry: Set policies for session data expiry and cleanup to manage resource usage
effectively.

- Consider Data Sharding: Depending on the size and nature of your application, consider sharding or
partitioning your data for better performance and scalability.

- Monitor and Optimize: Regularly monitor and optimize your chosen solution based on usage patterns
and requirements.

Select the technology based on your application's needs, scalability requirements, and the type of data
you are managing in the session. The examples provided use Python, but Azure SDKs are available
for various programming languages.
1. Azure Components Setup: Azure Redis Cache: - Create an Azure Redis Cache instance. - Obtain the connection string for the Redis Cache. Azure App Service (Web App): - Create an Azure App Service (Web App) to host your Flask application. - Configure the Flask application to connect to the Azure Redis Cache for session storage. Azure API Management (Optional): - Set up Azure API Management if you want to manage and secure your API. 2. Flask Application Setup: [below we will see in different use cases] Flask App with Flask-Session: - Create a Flask application with user authentication. - Use the `Flask-Session` extension to store session data in Redis. - Install required packages: ```bash pip install Flask Flask-Session redis ``` - Sample Flask App Code: ```python from flask import Flask, session from flask_session import Session import os app = Flask(__name__) # Configure session to use Redis app.config['SESSION_TYPE'] = 'redis' app.config['SESSION_PERMANENT'] = False app.config['SESSION_USE_SIGNER'] = True app.config['SESSION_KEY_PREFIX'] = 'your_prefix' app.config['SESSION_REDIS'] = 'your_redis_cache_connection_string' # Initialize the session extension Session(app) @app.route('/') def index(): if 'username' in session: return f'Logged in as {session["username"]}' return 'You are not logged in' @app.route('/login/<username>') def login(username): session['username'] = username return f'Logged in as {username}' @app.route('/logout') def logout(): session.pop('username', None) return 'Logged out' if __name__ == '__main__': app.secret_key = os.urandom(24) app.run(debug=True) ``` 3. Azure App Service Deployment: - Deploy your Flask application to the Azure App Service. 4. API Gateway and Load Balancer (Optional): - If using Azure API Management or Load Balancer, configure them to route traffic to your App
Service. 5. JWT Token Management: Description: JSON Web Tokens (JWT) can be used for secure communication and session management between
the front end, Flask API, and Azure Other Services.
Configuration: Use a library like PyJWT in your Flask API to generate and validate JWT tokens. Include the JWT token in requests from the front end to the Flask API and from the Flask API to
the Azure other Service.


5. Testing: - Test user login and session management functionality by accessing the endpoints of your Flask
application. 6. Secure Communication: - Ensure that all communication between the client and your Flask application and between
the application and Azure Redis Cache is secured using HTTPS.
Note: - Replace `'your_redis_cache_connection_string'` with the actual connection string of your Azure
Redis Cache. - If using Azure API Management or Load Balancer, configure them based on your specific
requirements. - Adjust session management settings (e.g., session timeout, secure cookie settings) based on your
application's needs.
Managing sessions using an API Gateway and Redis involves configuring the API Gateway to
handle user requests, directing traffic to backend services (like a Flask application), and using
Redis to store and manage session data. Below is a high-level guide on how this can be achieved:

1. API Gateway Configuration:

Azure API Management:
   - Create an Azure API Management instance.
   - Define API operations that correspond to your user authentication and session management
endpoints.
   - Configure policies in API Management to manage session tokens, validate tokens, and route requests.

Policies Example (in API Management):
   ```xml
   <!-- Validate JWT token policy -->
   <inbound>
      <base />
      <validate-jwt header-name="Authorization" failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized. Access token is missing or invalid."
require-expiration-time="false" require-signed-tokens="false" require-expiration-time="true">
         <openid-config url="https://your-authorization-server/.well-known/openid-configuration" />
         <audiences>
            <audience>your-audience</audience>
         </audiences>
      </validate-jwt>
   </inbound>

   <!-- Set backend service and add session ID to request -->
   <backend>
      <base />
      <set-backend-service base-url="https://your-backend-service" />
      <rewrite-uri template="/{session-id}/{path}" />
   </backend>
   ```

2. Flask Application with Redis:

   - Configure your Flask application to use Redis for session storage.
   - Adjust the Flask app code provided in the previous example to handle requests from the API
Gateway.

3. Redis Session Management:

   - Use Redis to store and manage session data. When a user logs in, store their session information
(e.g., user ID, session token) in Redis.

   - Example Redis Commands (Python with `redis` library):
     ```python
     import redis

     # Connect to Redis
     redis_client = redis.StrictRedis(host='your-redis-host', port=6379, decode_responses=True)

     # Set session data
     redis_client.set('session_id', 'user_data')

     # Get session data
     session_data = redis_client.get('session_id')
     ```

4. Secure Communication:

   - Ensure that communication between the API Gateway, Flask application, and Redis is secured.
Use HTTPS, secure Redis connections, and validate tokens in the API Gateway.

5. Testing:

   - Test the integration by making requests to the API Gateway, which in turn routes requests to the
Flask application and manages sessions using Redis.

6. Load Balancer (Optional):

   - If you have multiple instances of your Flask application, consider using a load balancer to distribute
traffic evenly.

7. JWT Token Management:

import jwt from flask import Flask, request app = Flask(__name__) SECRET_KEY = 'your_secret_key' @app.route('/generate_token/<user_id>/<app_id>') def generate_token(user_id, app_id): # Check if the provided app_id is associated with the user_id # You can implement your logic here to verify the association if is_valid_association(user_id, app_id): token_payload = {'user_id': user_id, 'app_id': app_id} token = jwt.encode(token_payload, SECRET_KEY, algorithm='HS256') return {'token': token} else: return {'error': 'Invalid association between user_id and app_id'} @app.route('/verify_token', methods=['POST']) def verify_token(): token = request.json['token'] try: decoded_token = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) return {'user_id': decoded_token['user_id'], 'app_id': decoded_token['app_id']} except jwt.ExpiredSignatureError: return {'error': 'Token has expired'} except jwt.InvalidTokenError: return {'error': 'Invalid token'} def is_valid_association(user_id, app_id): # Implement your logic to check if the provided app_id is associated with the user_id # For example, you might have a database where you store user and app associations # Return True if the association is valid, otherwise return False return True if __name__ == '__main__': app.run(debug=True)


In this example: The /generate_token/<user_id>/<app_id> endpoint now takes both user_id and app_id as parameters. There is a function is_valid_association that you should implement to check if the provided app_id
is associated with the given user_id. The generate_token endpoint generates a JWT token only if the association is valid. The /verify_token endpoint now returns both user_id and app_id from the decoded token. Remember to replace 'your_secret_key' with your actual secret key, and customize the
is_valid_association function based on your application's logic for associating users with appIDs.

Important links:




House Based Manufacturing Micro Clustering

                                 image generated by meta ai House-based manufacturing micro-clustering in China refers to the hyper-local, v...