CoolFace
Apppublic

TiGa-RCE/embedding-quantization-cuda-control

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes
app.py556 linesDownload Raw Back to root
1from __future__ import annotations2 3import json4import os5import time6import gc7import traceback8from pathlib import Path9 10import gradio as gr11import numpy as np12import spaces13import torch14import torch.nn.functional as F15from transformers import AutoModel, AutoTokenizer, BitsAndBytesConfig16 17 18MODEL_PATH = Path("/models/qwen3-embedding-0.6b")19DATA_ROOT = Path(os.environ.get("REPRO_DATA_ROOT", "/data"))20INPUT_PATH = DATA_ROOT / "inputs/retrieval_pairs.json"21MLX_REFERENCE = DATA_ROOT / "local-reference/qwen3-embedding-0.6b/bf16.npz"22OUTPUT_DIR = DATA_ROOT / "cloud-results/qwen3-embedding-0.6b"23CUDA_BF16_REFERENCE = OUTPUT_DIR / "cuda-bf16.npz"24TASK = (25    "Given a natural-language search query, retrieve the single passage "26    "that best answers it"27)28 29 30tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, padding_side="left")31# The completed 0.6B phase is intentionally not resident while the larger GTE32# startup path is validated.  Packing both models together exceeded the33# current Space startup envelope at 6.16 GB.34model = None35 36# ZeroGPU optimizes CUDA placements made during module startup.  The previous37# larger-family path loaded this model inside the decorated call and exhausted38# that reservation before producing an artifact.39GTE_MODEL_PATH = Path("/models/gte-qwen2-1.5b")40gte_tokenizer = None41gte_model = None42 43QWEN8_MODEL_PATH = Path("/models/qwen3-embedding-8b")44qwen8_tokenizer = AutoTokenizer.from_pretrained(QWEN8_MODEL_PATH, padding_side="left")45qwen8_model = None46 47 48def detailed_instruction(query: str) -> str:49    return f"Instruct: {TASK}\nQuery:{query}"50 51 52def encode_with(active_model, text: str, active_tokenizer=None) -> np.ndarray:53    active_tokenizer = active_tokenizer or tokenizer54    batch = active_tokenizer(55        text, return_tensors="pt", truncation=True, max_length=3276856    )57    batch = {key: value.to("cuda") for key, value in batch.items()}58    with torch.inference_mode():59        output = active_model(**batch, use_cache=False)60        vector = F.normalize(output.last_hidden_state[:, -1, :].float(), p=2, dim=-1)61    return vector[0].cpu().numpy()62 63 64def encode(text: str) -> np.ndarray:65    return encode_with(model, text)66 67 68def retrieval_metrics(queries: np.ndarray, documents: np.ndarray) -> tuple[dict, np.ndarray, np.ndarray]:69    scores = queries @ documents.T70    order = np.argsort(-scores, axis=1)71    ranks = np.array([72        int(np.where(order[index] == index)[0][0]) + 173        for index in range(len(queries))74    ])75    positive = np.diag(scores)76    negative = scores.copy()77    np.fill_diagonal(negative, -np.inf)78    margins = positive - negative.max(axis=1)79    return {80        "pair_count": len(queries),81        "top1": float(np.mean(ranks == 1)),82        "recall_at_5": float(np.mean(ranks <= 5)),83        "mrr": float(np.mean(1.0 / ranks)),84        "mean_margin": float(margins.mean()),85        "minimum_margin": float(margins.min()),86        "mean_rank": float(ranks.mean()),87        "worst_rank": int(ranks.max()),88    }, scores, ranks89 90 91@spaces.GPU(duration=300)92def run_bf16_control() -> dict:93    if model is None:94        raise RuntimeError("0.6B BF16 control is offline during the GTE phase")95    pairs = json.loads(INPUT_PATH.read_text())96    query_texts = [detailed_instruction(item["query"]) for item in pairs]97    document_texts = [item["document"] for item in pairs]98 99    if torch.cuda.is_available():100        torch.cuda.reset_peak_memory_stats()101        torch.cuda.synchronize()102    started = time.perf_counter()103    queries = np.stack([encode(text) for text in query_texts])104    documents = np.stack([encode(text) for text in document_texts])105    if torch.cuda.is_available():106        torch.cuda.synchronize()107    elapsed = time.perf_counter() - started108    metrics, scores, ranks = retrieval_metrics(queries, documents)109    metrics.update({110        "lane": "cuda-zerogpu-bf16",111        "model": "Qwen/Qwen3-Embedding-0.6B",112        "source_revision": "97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3",113        "elapsed_seconds": elapsed,114        "texts_per_second": len(query_texts + document_texts) / elapsed,115        "torch_version": torch.__version__,116        "cuda_device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else None,117        "cuda_peak_bytes": torch.cuda.max_memory_allocated() if torch.cuda.is_available() else None,118    })119 120    comparison = None121    if MLX_REFERENCE.exists():122        reference = np.load(MLX_REFERENCE)123        ref_all = np.concatenate([reference["queries"], reference["documents"]])124        cuda_all = np.concatenate([queries, documents])125        aligned = np.sum(ref_all * cuda_all, axis=1)126        score_delta = scores - reference["scores"]127        comparison = {128            "mean_aligned_cosine_cuda_vs_mlx_bf16": float(aligned.mean()),129            "minimum_aligned_cosine_cuda_vs_mlx_bf16": float(aligned.min()),130            "score_rmse_cuda_vs_mlx_bf16": float(np.sqrt(np.mean(score_delta ** 2))),131            "queries_with_rank_change": int(np.count_nonzero(ranks - reference["ranks"])),132        }133 134    result = {"metrics": metrics, "mlx_bf16_comparison": comparison}135    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)136    np.savez_compressed(137        OUTPUT_DIR / "cuda-bf16.npz",138        queries=queries,139        documents=documents,140        scores=scores,141        ranks=ranks,142        metrics=np.array(json.dumps(metrics)),143    )144    (OUTPUT_DIR / "cuda-bf16.json").write_text(json.dumps(result, indent=2) + "\n")145    return result146 147 148def compare_vectors(149    queries: np.ndarray,150    documents: np.ndarray,151    scores: np.ndarray,152    ranks: np.ndarray,153    reference_path: Path,154    prefix: str,155) -> dict | None:156    if not reference_path.exists():157        return None158    reference = np.load(reference_path)159    ref_all = np.concatenate([reference["queries"], reference["documents"]])160    candidate_all = np.concatenate([queries, documents])161    aligned = np.sum(ref_all * candidate_all, axis=1)162    score_delta = scores - reference["scores"]163    return {164        f"mean_aligned_cosine_{prefix}": float(aligned.mean()),165        f"minimum_aligned_cosine_{prefix}": float(aligned.min()),166        f"score_rmse_{prefix}": float(np.sqrt(np.mean(score_delta ** 2))),167        f"queries_with_rank_change_{prefix}": int(168            np.count_nonzero(ranks - reference["ranks"])169        ),170    }171 172 173QUANTIZERS = ("bnb-int8", "bnb-nf4")174 175 176def quantization_config(variant: str) -> BitsAndBytesConfig:177    if variant == "bnb-int8":178        return BitsAndBytesConfig(load_in_8bit=True)179    if variant == "bnb-nf4":180        return BitsAndBytesConfig(181            load_in_4bit=True,182            bnb_4bit_quant_type="nf4",183            bnb_4bit_compute_dtype=torch.bfloat16,184            bnb_4bit_use_double_quant=False,185        )186    raise ValueError(f"unsupported quantizer: {variant}")187 188 189@spaces.GPU(duration=300)190def run_quantized_control(variant: str) -> dict:191    if variant not in QUANTIZERS:192        raise ValueError(f"unsupported quantizer: {variant}")193    config = quantization_config(variant)194    pairs = json.loads(INPUT_PATH.read_text())195    query_texts = [detailed_instruction(item["query"]) for item in pairs]196    document_texts = [item["document"] for item in pairs]197 198    gc.collect()199    torch.cuda.empty_cache()200    torch.cuda.reset_peak_memory_stats()201    torch.cuda.synchronize()202    allocation_before = int(torch.cuda.memory_allocated())203    load_started = time.perf_counter()204    quantized_model = AutoModel.from_pretrained(205        MODEL_PATH,206        quantization_config=config,207        device_map={"": 0},208        trust_remote_code=True,209    ).eval()210    torch.cuda.synchronize()211    load_seconds = time.perf_counter() - load_started212    allocation_after_load = int(torch.cuda.memory_allocated())213 214    encode_started = time.perf_counter()215    queries = np.stack([encode_with(quantized_model, text) for text in query_texts])216    documents = np.stack([217        encode_with(quantized_model, text) for text in document_texts218    ])219    torch.cuda.synchronize()220    encode_seconds = time.perf_counter() - encode_started221    metrics, scores, ranks = retrieval_metrics(queries, documents)222    metrics.update({223        "lane": f"cuda-zerogpu-{variant}",224        "model": "Qwen/Qwen3-Embedding-0.6B",225        "source_revision": "97b0c614be4d77ee51c0cef4e5f07c00f9eb65b3",226        "quantizer": variant,227        "quantization_config": config.to_dict(),228        "load_seconds": load_seconds,229        "encode_seconds": encode_seconds,230        "texts_per_second": len(query_texts + document_texts) / encode_seconds,231        "torch_version": torch.__version__,232        "cuda_device": torch.cuda.get_device_name(0),233        "cuda_allocation_before_load": allocation_before,234        "cuda_allocation_after_load": allocation_after_load,235        "cuda_incremental_model_allocation": allocation_after_load - allocation_before,236        "cuda_peak_bytes": int(torch.cuda.max_memory_allocated()),237    })238    result = {239        "metrics": metrics,240        "cuda_bf16_comparison": compare_vectors(241            queries, documents, scores, ranks, CUDA_BF16_REFERENCE, "vs_cuda_bf16"242        ),243        "mlx_bf16_comparison": compare_vectors(244            queries, documents, scores, ranks, MLX_REFERENCE, "vs_mlx_bf16"245        ),246    }247    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)248    stem = f"cuda-{variant}"249    np.savez_compressed(250        OUTPUT_DIR / f"{stem}.npz",251        queries=queries,252        documents=documents,253        scores=scores,254        ranks=ranks,255        metrics=np.array(json.dumps(metrics)),256    )257    (OUTPUT_DIR / f"{stem}.json").write_text(json.dumps(result, indent=2) + "\n")258 259    del quantized_model260    gc.collect()261    torch.cuda.empty_cache()262    return result263 264 265BF16_FAMILIES = {266    "gte-qwen2-1.5b": {267        "path": Path("/models/gte-qwen2-1.5b"),268        "source": "Alibaba-NLP/gte-Qwen2-1.5B-instruct",269        "revision": "a9af15a6372d7d6b25e9fb07c2ccb9e1fe645644",270    },271    "qwen3-embedding-8b": {272        "path": Path("/models/qwen3-embedding-8b"),273        "source": "Qwen/Qwen3-Embedding-8B",274        "revision": "1d8ad4ca9b3dd8059ad90a75d4983776a23d44af",275    },276}277 278 279def _run_family_bf16_control(family: str) -> dict:280    if family not in BF16_FAMILIES:281        raise ValueError(f"unsupported family: {family}")282    spec = BF16_FAMILIES[family]283    pairs = json.loads(INPUT_PATH.read_text())284    query_texts = [detailed_instruction(item["query"]) for item in pairs]285    document_texts = [item["document"] for item in pairs]286 287    torch.cuda.reset_peak_memory_stats()288    torch.cuda.synchronize()289    allocation_before = int(torch.cuda.memory_allocated())290    family_tokenizer = AutoTokenizer.from_pretrained(291        spec["path"], padding_side="left", trust_remote_code=True292    )293    load_started = time.perf_counter()294    family_model = AutoModel.from_pretrained(295        spec["path"],296        dtype=torch.bfloat16,297        trust_remote_code=True,298        low_cpu_mem_usage=True,299        device_map={"": "cuda"},300    ).eval()301    torch.cuda.synchronize()302    load_seconds = time.perf_counter() - load_started303    loading_strategy = "in-call-direct-cuda-device-map"304    owns_model = True305    allocation_after_load = int(torch.cuda.memory_allocated())306 307    encode_started = time.perf_counter()308    queries = np.stack([309        encode_with(family_model, text, family_tokenizer) for text in query_texts310    ])311    documents = np.stack([312        encode_with(family_model, text, family_tokenizer) for text in document_texts313    ])314    torch.cuda.synchronize()315    encode_seconds = time.perf_counter() - encode_started316    metrics, scores, ranks = retrieval_metrics(queries, documents)317    metrics.update({318        "lane": "cuda-zerogpu-bf16",319        "family": family,320        "model": spec["source"],321        "source_revision": spec["revision"],322        "load_seconds": load_seconds,323        "loading_strategy": loading_strategy,324        "encode_seconds": encode_seconds,325        "texts_per_second": len(query_texts + document_texts) / encode_seconds,326        "torch_version": torch.__version__,327        "cuda_device": torch.cuda.get_device_name(0),328        "cuda_allocation_before_load": allocation_before,329        "cuda_allocation_after_load": allocation_after_load,330        "cuda_incremental_model_allocation": allocation_after_load - allocation_before,331        "cuda_peak_bytes": int(torch.cuda.max_memory_allocated()),332    })333    local_reference = (334        DATA_ROOT / "local-results/full-q4-q6-q8" / family / "quality/bf16.npz"335    )336    result = {337        "metrics": metrics,338        "mlx_bf16_comparison": compare_vectors(339            queries, documents, scores, ranks, local_reference, "cuda_vs_mlx_bf16"340        ),341        "previous_cuda_bf16_comparison": compare_vectors(342            queries,343            documents,344            scores,345            ranks,346            DATA_ROOT / "cloud-results" / family / "cuda-bf16-root-pack.npz",347            "direct_vs_root_pack_bf16",348        ),349    }350    output_dir = DATA_ROOT / "cloud-results" / family351    output_dir.mkdir(parents=True, exist_ok=True)352    np.savez_compressed(353        output_dir / "cuda-bf16.npz",354        queries=queries,355        documents=documents,356        scores=scores,357        ranks=ranks,358        metrics=np.array(json.dumps(metrics)),359    )360    (output_dir / "cuda-bf16.json").write_text(json.dumps(result, indent=2) + "\n")361 362    if owns_model:363        del family_model, family_tokenizer364        gc.collect()365        torch.cuda.empty_cache()366    return result367 368 369@spaces.GPU(duration=300)370def run_family_bf16_control(family: str) -> dict:371    try:372        return _run_family_bf16_control(family)373    except Exception as error:374        return {375            "diagnostic_error": type(error).__name__,376            "diagnostic_message": str(error),377            "diagnostic_traceback": traceback.format_exc(),378        }379 380 381def _run_family_quantized_control(family: str, variant: str) -> dict:382    if family not in BF16_FAMILIES:383        raise ValueError(f"unsupported family: {family}")384    if variant not in QUANTIZERS:385        raise ValueError(f"unsupported quantizer: {variant}")386 387    spec = BF16_FAMILIES[family]388    config = quantization_config(variant)389    pairs = json.loads(INPUT_PATH.read_text())390    query_texts = [detailed_instruction(item["query"]) for item in pairs]391    document_texts = [item["document"] for item in pairs]392    family_tokenizer = AutoTokenizer.from_pretrained(393        spec["path"], padding_side="left", trust_remote_code=True394    )395 396    gc.collect()397    torch.cuda.empty_cache()398    torch.cuda.reset_peak_memory_stats()399    torch.cuda.synchronize()400    allocation_before = int(torch.cuda.memory_allocated())401    load_started = time.perf_counter()402    family_model = AutoModel.from_pretrained(403        spec["path"],404        quantization_config=config,405        device_map={"": 0},406        trust_remote_code=True,407    ).eval()408    torch.cuda.synchronize()409    load_seconds = time.perf_counter() - load_started410    allocation_after_load = int(torch.cuda.memory_allocated())411 412    encode_started = time.perf_counter()413    queries = np.stack([414        encode_with(family_model, text, family_tokenizer) for text in query_texts415    ])416    documents = np.stack([417        encode_with(family_model, text, family_tokenizer) for text in document_texts418    ])419    torch.cuda.synchronize()420    encode_seconds = time.perf_counter() - encode_started421    metrics, scores, ranks = retrieval_metrics(queries, documents)422    metrics.update({423        "lane": f"cuda-zerogpu-{variant}",424        "family": family,425        "model": spec["source"],426        "source_revision": spec["revision"],427        "quantizer": variant,428        "quantization_config": config.to_dict(),429        "load_seconds": load_seconds,430        "encode_seconds": encode_seconds,431        "texts_per_second": len(query_texts + document_texts) / encode_seconds,432        "torch_version": torch.__version__,433        "cuda_device": torch.cuda.get_device_name(0),434        "cuda_allocation_before_load": allocation_before,435        "cuda_allocation_after_load": allocation_after_load,436        "cuda_incremental_model_allocation": allocation_after_load - allocation_before,437        "cuda_peak_bytes": int(torch.cuda.max_memory_allocated()),438    })439    output_dir = DATA_ROOT / "cloud-results" / family440    result = {441        "metrics": metrics,442        "cuda_bf16_comparison": compare_vectors(443            queries,444            documents,445            scores,446            ranks,447            output_dir / "cuda-bf16.npz",448            "vs_cuda_bf16",449        ),450        "mlx_bf16_comparison": compare_vectors(451            queries,452            documents,453            scores,454            ranks,455            DATA_ROOT / "local-results/full-q4-q6-q8" / family / "quality/bf16.npz",456            "vs_mlx_bf16",457        ),458    }459    output_dir.mkdir(parents=True, exist_ok=True)460    stem = f"cuda-{variant}"461    np.savez_compressed(462        output_dir / f"{stem}.npz",463        queries=queries,464        documents=documents,465        scores=scores,466        ranks=ranks,467        metrics=np.array(json.dumps(metrics)),468    )469    (output_dir / f"{stem}.json").write_text(json.dumps(result, indent=2) + "\n")470 471    del family_model, family_tokenizer472    gc.collect()473    torch.cuda.empty_cache()474    return result475 476 477@spaces.GPU(duration=300)478def run_family_quantized_control(family: str, variant: str) -> dict:479    try:480        return _run_family_quantized_control(family, variant)481    except Exception as error:482        return {483            "diagnostic_error": type(error).__name__,484            "diagnostic_message": str(error),485            "diagnostic_traceback": traceback.format_exc(),486        }487 488 489with gr.Blocks() as demo:490    gr.Markdown(491        "# Embedding Quantization CUDA Control\n"492        "Runs one bounded 48-text BF16 control and writes the result to the attached private bucket. "493        "The requested GPU duration is capped at five minutes."494    )495    run_button = gr.Button("Run 0.6B BF16 CUDA control", variant="primary")496    output = gr.JSON(label="Result")497    run_button.click(498        fn=run_bf16_control,499        outputs=output,500        concurrency_limit=1,501        api_name="run_bf16_control",502    )503    gr.Markdown(504        "## CUDA-native quantizer controls\n"505        "These are bitsandbytes INT8/NF4 controls, not MLX Q/oQ/oQe replicas."506    )507    quantizer = gr.Dropdown(508        choices=list(QUANTIZERS), value="bnb-int8", label="Quantizer"509    )510    quant_button = gr.Button("Run bounded CUDA quantizer control")511    quant_output = gr.JSON(label="Quantized result")512    quant_button.click(513        fn=run_quantized_control,514        inputs=quantizer,515        outputs=quant_output,516        concurrency_limit=1,517        api_name="run_quantized_control",518    )519    gr.Markdown("## Larger-family BF16 cross-runtime controls")520    family = gr.Dropdown(521        choices=list(BF16_FAMILIES),522        value="gte-qwen2-1.5b",523        label="Model family",524    )525    family_button = gr.Button("Run bounded family BF16 control")526    family_output = gr.JSON(label="Family result")527    family_button.click(528        fn=run_family_bf16_control,529        inputs=family,530        outputs=family_output,531        concurrency_limit=1,532        api_name="run_family_bf16_control",533    )534    gr.Markdown("## Larger-family CUDA-native quantizer controls")535    quant_family = gr.Dropdown(536        choices=list(BF16_FAMILIES),537        value="qwen3-embedding-8b",538        label="Model family",539    )540    family_quantizer = gr.Dropdown(541        choices=list(QUANTIZERS), value="bnb-int8", label="Quantizer"542    )543    family_quant_button = gr.Button("Run bounded family quantizer control")544    family_quant_output = gr.JSON(label="Family quantized result")545    family_quant_button.click(546        fn=run_family_quantized_control,547        inputs=[quant_family, family_quantizer],548        outputs=family_quant_output,549        concurrency_limit=1,550        api_name="run_family_quantized_control",551    )552 553 554if __name__ == "__main__":555    demo.queue(default_concurrency_limit=1).launch()556