"""HTML rendering for the inference demo UI. Styling follows the Liquid AI design system from liquid-lfm-cloud: - Monochrome primary scale (#171717 primary, #f5f5f5 background) - Semantic accents: emerald (#10B981), amber (#F59E0B), red (#EF4444), blue (#3B82F6) - JetBrains Mono for technical/metric elements, system sans-serif for body - 16px rounded cards with subtle shadows - Pill-shaped buttons and badges """ from __future__ import annotations from typing import Callable import numpy as np from src.data.generator import AMOUNT_RANGE_LABELS from src.data.schema import VALUES_START from src.demo.decode import TransactionDecoder from src.demo.merchant_catalog import DemoMerchantCatalog, MCC_NAMES # Liquid design tokens — light mode (liquid-lfm-cloud design system) _BG = "#f5f5f5" _BG_CARD = "#ffffff" _BG_CARD_ALT = "#fafafa" _BORDER = "rgba(0,0,0,0.1)" _BORDER_SUBTLE = "rgba(0,0,0,0.05)" _TEXT = "#171717" _TEXT_MUTED = "#525252" _TEXT_DIM = "#737373" _ACCENT_BLUE = "#3B82F6" _ACCENT_GREEN = "#10B981" _ACCENT_AMBER = "#F59E0B" _ACCENT_RED = "#EF4444" _RADIUS_CARD = "16px" _RADIUS_SM = "8px" _FONT_MONO = "JetBrains Mono, ui-monospace, SFMono-Regular, monospace" _FONT_SANS = "-apple-system, BlinkMacSystemFont, Segoe UI, Roboto, sans-serif" def format_fraud_score(prob: float) -> str: """Format fraud probability as colored gauge bar.""" pct = prob * 100 if pct < 20: color = _ACCENT_GREEN risk = "LOW RISK" elif pct < 60: color = _ACCENT_AMBER risk = "MEDIUM RISK" else: color = _ACCENT_RED risk = "HIGH RISK" bar_width = max(2, min(100, int(pct))) return f"""
{pct:.1f}% {risk}
""" def format_topk_predictions( probs: np.ndarray, k: int, label_fn: Callable[[int], str], ) -> str: """Format top-k predictions as styled rows with probability bars.""" top_indices = np.argsort(probs)[::-1][:k] rows = "" for i, idx in enumerate(top_indices): p = probs[idx] * 100 label = label_fn(int(idx)) bar_width = max(2, int(p * 2.5)) opacity = 1.0 - i * 0.15 weight = "600" if i == 0 else "400" # Show "<0.1%" for probabilities that round to 0.0% at 1 decimal place. # This happens with high-cardinality heads (10K merchants) when the model # hasn't learned a meaningful distribution (e.g. random-init baseline). p_str = f"{p:.1f}%" if p >= 0.05 else "<0.1%" rows += f"""
{label}
{p_str}
""" return f'
{rows}
' def format_merchant_predictions( probs: np.ndarray, merchant_catalog: DemoMerchantCatalog, k: int = 5, ) -> str: """Top-k merchant predictions with names and categories.""" def label_fn(idx: int) -> str: if idx < VALUES_START: return "[special]" mid = idx - VALUES_START info = merchant_catalog.get(mid) return f"{info.name} ({info.category})" return format_topk_predictions(probs, k, label_fn) def format_amount_predictions(probs: np.ndarray, k: int = 5) -> str: """Top-k amount range predictions.""" def label_fn(idx: int) -> str: return AMOUNT_RANGE_LABELS.get(idx, f"Range {idx}") return format_topk_predictions(probs, k, label_fn) def format_mcc_predictions(probs: np.ndarray, k: int = 5) -> str: """Top-k MCC predictions with category names.""" def label_fn(idx: int) -> str: if idx < VALUES_START: return "[special]" mcc_val = idx - VALUES_START return MCC_NAMES.get(mcc_val, f"MCC-{mcc_val}") return format_topk_predictions(probs, k, label_fn) def format_timeline(decoder: TransactionDecoder, token_ids: np.ndarray) -> str: """Render transaction sequence as a scrollable table.""" txns = decoder.decode_sequence(token_ids) header = f"""
""" rows = "" for txn in txns: when = f"{txn.dow} {txn.hour}" highlight = f"background: rgba(59, 130, 246, 0.08);" if txn.index == 63 else "" rows += f""" """ return header + rows + "
Tx When Merchant Category Amount Method Country
{txn.index} {when} {txn.merchant_name} {txn.merchant_category} {txn.amount_range} {txn.entry_mode} {txn.country}
" def render_production_architecture() -> str: """LFM2.5 production architecture and how it applies to payments.""" _purple = "#7c3aed" _purple_bg = "rgba(124,58,237,0.08)" _purple_border = "rgba(124,58,237,0.25)" def _layer_cell(label: str, idx: int, is_attn: bool) -> str: bg = _purple_bg if is_attn else "rgba(16,185,129,0.08)" bc = _purple_border if is_attn else "rgba(16,185,129,0.25)" color = _purple if is_attn else _ACCENT_GREEN return f"""
{label}
L{idx}
""" layers_1_2b = [ ("C", 0, False), ("C", 1, False), ("A", 2, True), ("C", 3, False), ("C", 4, False), ("A", 5, True), ("C", 6, False), ("C", 7, False), ("A", 8, True), ("C", 9, False), ("A", 10, True), ("C", 11, False), ("A", 12, True), ("C", 13, False), ("A", 14, True), ("C", 15, False), ] layer_cells = "".join(_layer_cell(l, i, a) for l, i, a in layers_1_2b) return f"""

LFM2.5 for Payment Sequences

The published LFM2.5 architecture adapted for structured transaction data. Same hybrid conv-attention backbone. New per-feature embedding layer.

Production Target
Parameters 1.2B
Hidden dim 2048
Layers 10 conv + 6 attn
Attention 32Q / 8KV (GQA)
MLP SwiGLU 12288
Sequence 128 tx × 30 feat = 3,840
Latency target < 50ms (H100)
This Demo (Reference)
Parameters 9.8M
Hidden dim 256
Layers 5 conv + 3 attn
Attention 4Q / 2KV (GQA)
MLP SwiGLU 1024
Sequence 64 tx × 15 feat = 960
Measured latency < 80ms (CPU)
LFM2.5-1.2B Layer Pattern (16 layers)
{layer_cells}
■ Conv (10): local patterns, k=3, O(n) ■ Attention (6): global context, GQA, O(n²)
First and last layers are convolutional. Attention is densest mid-stack. The LM head reads from a local-conv output, but 6 attention layers have already encoded global context upstream.
Per-Feature Embedding

Text LFM2 has one embedding table. Payment LFM2 has one table per feature (hour, merchant, MCC, amount, ...) plus a feature-type table. Summed to produce the same (B, T, D) the backbone expects.

value_tables[f](token) + type_table(f)
KV-Cache Advantage

Only 6 of 16 layers need KV cache (attention layers only). A pure-attention model at the same depth needs 16 layers of cache. At 3,840 token sequences with 1,024 concurrent requests:

LFM2: ~25 GB vs Pure attn: ~64 GB
Weight-Tied Heads

Downstream heads that predict pretrained features (next merchant, MCC) project through the backbone's own embedding table. Zero extra parameters, +50% accuracy vs fresh MLP heads.

adapter(h) @ embedding.weight.T
How This Translates to Your Implementation
Schema
Your features, your vocab sizes, your ordering. We review the schema design.
Pretrain
Self-supervised on your unlabeled transactions. No fraud labels needed.
Fine-tune
Attach task heads (fraud, disputes, auth optimization). Multi-task with shared backbone.
Deploy
Your infra, your GPUs, your compliance. Sub-100ms for real-time authorization decisioning.
Own
No data leaves your infrastructure. No external API dependency. You own the model.
Architecture: arXiv 2511.23404 · Weights: huggingface.co/LiquidAI
""" def render_comparison_header() -> str: """Header explaining the pretrained vs random-init comparison.""" return f"""
Pretrained vs Random Init: Same architecture, same input, same fine-tuning data. Left: pretrained on 200K unlabeled sequences first. Right: trained from scratch. The difference is the value of self-supervised pretraining.
""" def render_why_liquid() -> str: """Render the 'Why Liquid AI' value proposition tab. Content drawn from docs/architecture-walkthrough.md §14. Uses measured reference-implementation numbers, not marketing claims. """ def _table_row(cells: list[str], bold_last: bool = False) -> str: tds = "" for i, c in enumerate(cells): weight = "600" if (bold_last and i == len(cells) - 1) else "400" align = "right" if i > 0 else "left" tds += ( f'{c}' ) return f"{tds}" def _table_header(cols: list[str]) -> str: ths = "" for i, c in enumerate(cols): align = "right" if i > 0 else "left" ths += ( f'{c}' ) return f"{ths}" return f"""

Why LFM2.5 for Transaction Sequences

Three claims, each backed by a different kind of evidence. Serving cost is arithmetic. Label efficiency is measured. Architectural fit is a first-principles argument.

1. Serving Cost Scales Better Than Pure Attention

10 of 16 layers use O(n) conv instead of O(n²) attention. The gap is structural and compounds as sequence length grows. Measured at matched parameter count on the same hardware:

{_table_header(["Sequence", "Tokens", "Hybrid", "Pure Attn", "Speedup"])} {_table_row(["64 tx × 15 feat", "960", "5.96 ms", "9.01 ms", "1.5x"])} {_table_row(["64 tx × 30 feat", "1,920", "13.05 ms", "23.84 ms", "1.8x"])} {_table_row(["128 tx × 30 feat", "3,840", "35.65 ms", "75.95 ms", "2.1x"], bold_last=False)} {_table_row(["256 tx × 30 feat", "7,680", "119 ms", "283 ms", "2.4x"], bold_last=False)}

At 3,840 tokens (production target), the FLOP analysis predicts 7.7% fewer operations. The actual speedup is larger because conv layers achieve better hardware utilization. At 7,680 tokens, savings reach 17% in FLOPs and 2.4x in wall-clock.

2. Pretraining Gets to 98% of Full-Data Quality with 10% of Labels

Self-supervised pretraining on unlabeled transactions. No fraud labels needed. Then fine-tune with whatever labels you have. The pretrained model at 10% labels beats the random-init baseline at 100% labels.

{_table_header(["Labels", "Sequences", "Pretrained PR-AUC", "Baseline PR-AUC", "Delta"])} {_table_row(["1%", "1,700", "0.539", "0.046", "+0.493"])} {_table_row(["10%", "17,000", "0.948", "0.690", "+0.258"])} {_table_row(["100%", "170,000", "0.964", "0.922", "+0.041"])}

At 1% labels the baseline has learned nothing (0.046 is random guessing). The pretrained model is already useful. This is the defining advantage for institutions with billions of unlabeled transactions and limited fraud labels.

5x Convergence Speed

The pretrained model hit 0.959 PR-AUC at step 1,000. The random-init baseline never reached that level in 5,000 steps (peaked at 0.922).

For quarterly model refreshes, this cuts GPU-hours per cycle by 80%. For weekly refreshes, it is the difference between feasible and infeasible.

One Backbone, Many Tasks

A single forward pass serves fraud detection, next-merchant prediction, amount forecasting, and merchant category classification. Add dispute prediction, default risk, or authorization optimization as new heads without retraining the backbone.

Tied-embedding heads: +50% merchant accuracy at zero parameter cost

3. The Architecture Matches Transaction Data Structure

Transaction data is not like text. Information density is concentrated locally (within-transaction feature correlations, adjacent-transaction continuity) with sparse global signal (behavioral baselines across the full history). LFM2.5 allocates O(n) conv to the dense local patterns and O(n²) attention to the sparse global patterns. A pure transformer allocates O(n²) compute uniformly across all distances.

Within Transaction
Merchant determines MCC. Entry mode correlates with amount. Dense, local, often deterministic. A 3-wide conv kernel captures this.
Adjacent Transactions
Strong temporal continuity. A customer at Starbucks is likely at a similar merchant next. The conditional distribution of t+1 given t is heavily peaked.
Distant Transactions
Weak but non-zero signal. Behavioral profile matters for fraud baseline, but per-position information density is thin. This is where attention earns its cost.

Your Data, Your Model, Your Infrastructure

Liquid licenses the architecture and training recipe. You train on your proprietary data behind your firewall. No data leaves your infrastructure. No dependency on external model APIs. The result is a foundation model you own, optimized for your specific transaction patterns, deployed on your hardware.

What We Claim vs What We Don't

We claim
  • Fewer FLOPs per forward pass above ~1,500 tokens (arithmetic)
  • Cost gap widens with sequence length (structural, measured 2.1-2.4x)
  • Pretraining dramatically improves low-label performance (measured)
  • Full pipeline works end-to-end on LFM2.5 (built it)
We don't claim
  • Quality advantage over pure transformers at matched scale
  • Absolute latency numbers extrapolated from 10M to 1.2B
  • Superiority on all tasks (the advantage is specific to structured sequences)
  • Real-data validation (all results are on synthetic data)
Source: arXiv 2511.23404 · Reference implementation measured at 9.85M params, Apple Silicon
""" def render_integration_guide() -> str: """Render high-abstraction integration architecture flow. Distills docs/integration-guide.md into a visual pipeline overview. Monochrome design -- no rainbow pills or per-card color coding. """ def _phase_card(num: str, title: str, body: str, detail: str) -> str: return f"""
{num} {title}

{body}

{detail}
""" def _gotcha(num: str, title: str, desc: str) -> str: return f"""
{num}.
{title} -- {desc}
""" return f"""

Integration Architecture

Seven phases from raw transaction data to production fraud scoring. Each phase has a clear input, output, and set of decisions.

Schema Tokenize Embed Backbone Pretrain Heads Deploy
{_phase_card("1", "Schema Design", "Define your features, vocab sizes, and feature ordering. This is the contract " "between your data team and your ML team. Three feature types: categorical " "(direct index), ordinal (hour, day-of-week), and bucketed-continuous (amount " "into quantile bins). Spend two weeks here.", "3 reserved tokens per feature (MASK, OOV, NULL)  |  " "Order features by semantic family for the conv window  |  " "Embed schema fingerprint in checkpoint metadata")} {_phase_card("2", "Tokenization", "Convert raw transaction fields into integer token IDs. Categorical features " "map directly to vocab indices. Continuous values (amount, days-since-last) get " "quantile-bucketed into N bins. High-cardinality features (merchant_id) need the " "long tail bucketed or factored into orthogonal features.", "amount → 16-256 quantile bins  |  " "merchant_id: top 10K distinct, rest into ~1K frequency buckets  |  " "Unseen values at inference → OOV token (ID 1)")} {_phase_card("3", "Structured Embedding", "One embedding table per feature (sized to its vocab) plus a feature-type table. " "Summed to produce the (B, T*F, D) tensor the backbone expects. " "No raw continuous features -- everything goes through an embedding table.", "value_tables[f](token) + type_table(f)  |  " "High-cardinality features dominate param budget -- " "bucket the merchant long tail")} {_phase_card("4", "Backbone Config", "Start from a published LFM2 scale point (350M, 700M, 1.2B, 2.6B). " "Keep the conv-to-attention ratio, GQA config, SwiGLU MLP, and RoPE theta. " "Do not deviate without a specific reason.", "10:6 conv:attn at 1.2B  |  32Q/8KV GQA  |  " "QK-RMSNorm before RoPE  |  theta=1M  |  " "First and last layers are conv")} {_phase_card("5", "Pretraining", "Self-supervised on your unlabeled transactions. No fraud labels needed. " "Causal next-feature prediction or masked-transaction prediction. " "Average per-feature losses (do NOT sum).", "AdamW lr=3e-4, betas=(0.9, 0.95), wd=0.1  |  " "Cosine decay to 10%  |  BF16 (loss in FP32)  |  " "Chinchilla: ~20 tokens per parameter")} {_phase_card("6", "Downstream Heads", "Attach task-specific heads. The critical decision: " "TiedEmbeddingHead for features that were in pretraining vocab " "(next_merchant, MCC). Fresh MLP for everything else (fraud, disputes).", "Tied head: adapter(h) @ embedding.weight.T (+50% accuracy)  |  " "Pool: last_tx_mean for sequence tasks, pre_last_tx for next-tx  |  " "Dual LR: backbone 5e-5, heads 1e-3")}
{_phase_card("7", "Deployment", "KV cache only on 6 attention layers (not 16). Dynamic batching with " "5-10ms collection window. Conv-dominant models quantize cleanly to INT8. " "Same model runs on GPU or CPU.", "LFM2 @ 3,840 tokens: ~25 GB KV at 1K concurrent  |  " "Pure attn: ~64 GB  |  " "Sub-100ms on H100 for real-time auth decisioning")}
Top Gotchas
{_gotcha("1", "Per-feature loss summing", "high-cardinality features dominate. Average, don't sum.")} {_gotcha("2", "Fresh MLP for pretrained features", "next_merchant stays at random. Use TiedEmbeddingHead.")} {_gotcha("3", "Schema-checkpoint drift", "training on schema v3, deploying on v2. Embed fingerprint in metadata.")} {_gotcha("4", "Frozen backbone", "PR-AUC drops from 0.96 to 0.16. Plan to fine-tune.")} {_gotcha("5", "Pool strategy leak", "last_tx_mean for next-tx prediction leaks the target. Use pre_last_tx.")}
Typical Engagement
Phase Duration What Happens
Discovery 2-4 weeks Schema definition, data sample (~1M sequences), compliance review, architectural fit assessment
POC 2 weeks Pretrain + fine-tune on your data sample, measurement report, go/no-go recommendation
Production 3-6 months Engineering team builds, Liquid provides architectural support, weekly design review
Scale Ongoing Operations, monitoring, retraining cadence, architecture evolution
Full guide: docs/integration-guide.md · Architecture: arXiv 2511.23404
"""