diff --git a/.gitignore b/.gitignore index 6cd2878d..96fe9dea 100644 --- a/.gitignore +++ b/.gitignore @@ -199,6 +199,8 @@ cython_debug/ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data # refer to https://docs.cursor.com/context/ignore-files .cursor/ +!ide-context/.cursor/ +!ide-context/.cursor/** .cursorignore .cursorindexingignore diff --git a/README.md b/README.md index ee5079f8..a339cc80 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,38 @@ lsof -t -i:8853 | xargs kill -9 # frontend server lsof -t -i:8855 | xargs kill -9 # ray dashboard ``` +## AI-Assisted Development (Claude Code & Cursor) + +Get instant IDE context that teaches Claude Code and Cursor the full RapidFire AI API, patterns, and common mistakes. + +**Run this from your project root** (the directory where you write your rapidfireai experiments): + +```bash +# Recommended: use the CLI you already have (works on Windows, macOS, Linux) +rapidfireai install-ide-context +``` + +This copies three files into your project: + +``` +your-project/ +├── CLAUDE.md ← Claude Code: loaded every session +├── .claude/rules/rapidfireai-api.md ← Claude Code: full API ref (on demand) +└── .cursor/rules/rapidfireai.mdc ← Cursor: auto-applied to .py / .ipynb +``` + +Fallback one-liners if you don't have the CLI yet: + +```bash +# macOS / Linux +curl -sSL https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context/install.sh | bash + +# Windows (PowerShell) +irm https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context/install.ps1 | iex +``` + +See [`ide-context/README.md`](ide-context/README.md) for details. + ## Documentation Browse or reference the full documentation, example use case tutorials, all API details, dashboard details, and more in the [RapidFire AI Documentation](https://oss-docs.rapidfire.ai). diff --git a/ide-context/.claude/rules/rapidfireai-api.md b/ide-context/.claude/rules/rapidfireai-api.md new file mode 100644 index 00000000..8fc5ff5b --- /dev/null +++ b/ide-context/.claude/rules/rapidfireai-api.md @@ -0,0 +1,340 @@ +# RapidFire AI — Full API Reference + +## Experiment Class + +```python +from rapidfireai import Experiment + +experiment = Experiment( + experiment_name: str, # Unique name; auto-suffixed if reused + mode: str = "fit", # "fit" or "evals" — cannot mix in same experiment + experiment_path: str = "./rapidfire_experiments" +) +``` + +**Methods:** + +| Method | Description | +|--------|-------------| +| `run_fit(param_config, create_model_fn, train_dataset, eval_dataset, num_chunks, seed=42, num_gpus=1)` | Launch multi-config training | +| `run_evals(config_group, dataset, num_shards=4, seed=42, num_actors=None, gpus_per_actor=None, cpus_per_actor=None) → dict` | Launch multi-config evals; returns `{run_id: (aggregated_metrics, cumulative_metrics)}` | +| `end()` | End experiment, clear system state | +| `get_runs_info() → pd.DataFrame` | Metadata for all runs (run_id, status, config, etc.) | +| `get_results() → pd.DataFrame` | All metrics for all steps for all runs | + +**Important:** Only one function runs at a time. After interrupting `run_fit()`/`run_evals()`, wait ~2 min before calling another function. + +--- + +## Multi-Config Specification + +### Knob Set Generators +```python +from rapidfireai.automl import List, Range + +List([val1, val2, val3]) # Discrete set; all same Python type +Range(start, end, dtype="int") # "int" or "float"; uniform sampling +``` + +### Config Group Generators +```python +from rapidfireai.automl import RFGridSearch, RFRandomSearch + +RFGridSearch( + configs: Dict | List[Dict], # Config dict with List() knobs (no Range allowed) + trainer_type: str = None # "SFT" | "DPO" | "GRPO" — omit for run_evals() +) # Note: trainer_type is required for fit mode; omit or set None for evals mode + + +RFRandomSearch( + configs: Dict, # Can have List() or Range() knobs + trainer_type: str = None, + num_runs: int = 1, # How many random combos to sample +) +``` + +**param_config** (for `run_fit()`) / **config_group** (for `run_evals()`) accepts: +- Single config dict +- List of config dicts +- `RFGridSearch()` / `RFRandomSearch()` output +- Mixed list of any of the above + +--- + +## SFT / RFT APIs + +### RFLoraConfig +Wraps HuggingFace `peft.LoraConfig`. Any arg can be `List()` or `Range()`. + +```python +from rapidfireai.automl import RFLoraConfig + +RFLoraConfig( + r=16, # Rank: 8–128, powers of 2 + lora_alpha=32, # Usually 2x r + lora_dropout=0.05, # 0.0–0.05 + target_modules=["q_proj", "v_proj"], # or add k,o,gate,up,down projections + bias="none", + init_lora_weights=True, # or "gaussian", "pissa" +) +``` + +### RFModelConfig +```python +from rapidfireai.automl import RFModelConfig + +RFModelConfig( + model_name: str, # HF hub name or local path + tokenizer: str = None, # Defaults to model_name + tokenizer_kwargs: dict = None, # padding_side, truncation, model_max_length + formatting_func: Callable | List, # Can be List for multi-config + compute_metrics: Callable | List, # Can be List for multi-config + peft_config: RFLoraConfig | List, # Can be List for multi-config + training_args, # RFSFTConfig | RFDPOConfig | RFGRPOConfig + model_type: str = "causal_lm", # Used inside your create_model_fn + model_kwargs: dict = None, # torch_dtype, device_map, use_cache, etc. + # DPO/GRPO only: + ref_model_name: str = None, + ref_model_type: str = None, + ref_model_kwargs: dict = None, + reward_funcs: Callable | List = None, + generation_config: dict = None, # max_new_tokens, temperature, top_p +) +``` + +### Trainer Configs (all args can be List/Range) +```python +from rapidfireai.automl import RFSFTConfig, RFDPOConfig, RFGRPOConfig + +# SFT — wraps HF TRL SFTConfig +RFSFTConfig(learning_rate, lr_scheduler_type, per_device_train_batch_size, + per_device_eval_batch_size, gradient_accumulation_steps, + num_train_epochs, logging_steps, eval_strategy, eval_steps, + fp16, save_strategy, ...) + +# DPO — wraps HF TRL DPOConfig +RFDPOConfig(beta, loss_type, model_adapter_name, ref_adapter_name, + max_prompt_length, max_completion_length, max_length, + per_device_train_batch_size, learning_rate, ...) + +# GRPO — wraps HF TRL GRPOConfig +RFGRPOConfig(learning_rate, num_generations, max_prompt_length, + max_completion_length, per_device_train_batch_size, ...) +``` + +### User-Provided Functions for run_fit() + +**create_model_fn** (mandatory, passed to `run_fit()`): +```python +def create_model_fn(model_config: dict) -> tuple[PreTrainedModel, PreTrainedTokenizer]: + model_name = model_config["model_name"] + model_type = model_config["model_type"] + model_kwargs = model_config["model_kwargs"] + # load model based on model_type... + return (model, tokenizer) +``` + +**compute_metrics_fn** (optional, passed to `RFModelConfig.compute_metrics`): +```python +def compute_metrics_fn(eval_preds: tuple) -> dict[str, float]: + predictions, labels = eval_preds + # return {"rougeL": 0.42, "bleu": 0.31} +``` + +**formatting_fn** (optional, passed to `RFModelConfig.formatting_func`): +```python +def formatting_fn(row: dict) -> dict: + return { + "prompt": [{"role": "system", "content": "..."}, {"role": "user", "content": row["input"]}], + "completion": [{"role": "assistant", "content": row["output"]}] + } +``` + +**reward_function** (GRPO, list passed to `RFModelConfig.reward_funcs`): +```python +def reward_fn(prompts, completions, completions_ids, trainer_state, **kwargs) -> list[float]: + # kwargs contains all dataset columns except "prompt" + return [1.0 if pred == gt else 0.0 for pred, gt in zip(completions, kwargs["answer"])] +``` + +--- + +## RAG / Context Engineering APIs + +### RFLangChainRagSpec +```python +from rapidfireai.automl import RFLangChainRagSpec + +rag = RFLangChainRagSpec( + document_loader, # LangChain BaseLoader + text_splitter, # LangChain TextSplitter — can be List() + embedding_cls=None, # Pass the CLASS, not an instance + embedding_kwargs=None, # Dict to init embedding_cls + vector_store=None, # Default: FAISS + retriever=None, # Custom retriever; default FAISS if None + search_type="similarity", # "similarity" | "similarity_score_threshold" | "mmr" + search_kwargs={"k": 5}, # k, filter, score_threshold, fetch_k, lambda_mult + reranker_cls=None, # Pass the CLASS — can have List() kwargs + reranker_kwargs=None, + enable_gpu_search=False, # True → FAISS GPU exact search + document_template=None, # Callable[[Document], str] +) + +# Helper methods: +rag.get_context(batch_queries, use_reranker=True, serialize=True) +rag.serialize_documents(batch_docs) # list[list[Document]] → list[str] +``` + +### RFvLLMModelConfig (self-hosted LLM) +```python +from rapidfireai.automl import RFvLLMModelConfig + +RFvLLMModelConfig( + model_config={ + "model": "Qwen/Qwen2.5-0.5B-Instruct", + "dtype": "half", + "gpu_memory_utilization": 0.7, + "tensor_parallel_size": 1, + "distributed_executor_backend": "mp", # only "mp" supported + "max_model_len": 2048, + }, + sampling_params={"temperature": 0.8, "top_p": 0.95, "max_tokens": 512}, + rag=rag_spec, # RFLangChainRagSpec or None + prompt_manager=None, # RFPromptManager or None +) +``` + +### RFOpenAIAPIModelConfig (OpenAI API) +```python +from rapidfireai.automl import RFOpenAIAPIModelConfig + +RFOpenAIAPIModelConfig( + client_config={"api_key": OPENAI_API_KEY, "max_retries": 2}, + model_config={ + "model": "gpt-4o", + "temperature": 0.2, + "max_completion_tokens": 1024, + "reasoning_effort": List(["medium", "high"]), # can be List + }, + rpm_limit=500, + tpm_limit=500_000, + max_completion_tokens=None, # Defaults to 150 if not set in model_config + rag=None, + prompt_manager=prompt_manager, +) +``` + +### RFPromptManager +```python +from rapidfireai.automl import RFPromptManager + +RFPromptManager( + instructions: str = None, # or instructions_file_path + instructions_file_path: str = None, + examples: list[dict] = None, # Few-shot examples + embedding_cls=None, + embedding_kwargs=None, + example_selector_cls=None, # SemanticSimilarityExampleSelector or MaxMarginalRelevanceExampleSelector + example_prompt_template=None, # LangChain PromptTemplate + k: int = 3, # Number of examples per prompt +) +``` + +### Other Eval Config Knobs (in config dict) +```python +config = { + "batch_size": 8, + "preprocess_fn": preprocess_fn, # mandatory + "postprocess_fn": postprocess_fn, # optional + "compute_metrics_fn": compute_metrics_fn,# mandatory + "accumulate_metrics_fn": accumulate_metrics_fn, # optional + "online_strategy_kwargs": { + "strategy_name": "normal", # "normal" | "wilson" | "hoeffding" + "confidence_level": 0.95, + "use_fpc": True, + }, +} +``` + +### User-Provided Functions for run_evals() + +**preprocess_fn** (mandatory): +```python +def preprocess_fn(batch: dict[str, list], rag: RFLangChainRagSpec, prompt_manager) -> dict[str, list]: + # Must return dict with "prompts" key containing list of formatted prompts + return {"prompts": [...], **batch} +``` + +**postprocess_fn** (optional): +```python +def postprocess_fn(batch: dict[str, list]) -> dict[str, list]: + # batch["generated_text"] contains model outputs + return batch +``` + +**compute_metrics_fn** (mandatory): +```python +def compute_metrics_fn(batch: dict[str, list]) -> dict[str, dict]: + # Returns per-batch metrics + return { + "Accuracy": {"value": correct / total}, + "Total": {"value": total}, + } +``` + +**accumulate_metrics_fn** (optional — if omitted, all metrics treated as distributive): +```python +def accumulate_metrics_fn(aggregated_metrics: dict[str, list[dict]]) -> dict[str, dict]: + # aggregated_metrics["MetricName"] = list of per-batch metric dicts + return { + "Accuracy": {"value": accuracy, "is_algebraic": True, "value_range": (0, 1)}, + "Total": {"value": total, "is_distributive": True, "value_range": (0, 1)}, + } +``` + +--- + +## IC Ops (Interactive Control) + +Access via dashboard at `http://0.0.0.0:8853` or in-notebook: +```python +from rapidfireai.fit.utils.interactive_controller import InteractiveController +controller = InteractiveController(dispatcher_url="http://127.0.0.1:8851") +controller.display() +``` + +| Op | When | Effect | +|----|------|--------| +| **Stop** | Any running run | Paused at next chunk boundary; no GPU used | +| **Resume** | Stopped run | Re-added to scheduler at next chunk | +| **Clone-Modify** | Any run | New run from modified knob config; can warm-start from parent weights | +| **Delete** | Any run | Removed from plots; checkpoints preserved on disk | + +IC Ops execute at chunk boundaries (not immediately). Warm-start only works if clone has identical architecture as parent. + +--- + +## Troubleshooting Quick Reference + +```bash +rapidfireai doctor # Full diagnostic report + +# Kill port conflicts: +lsof -t -i:8852 | xargs kill -9 # mlflow +lsof -t -i:8851 | xargs kill -9 # dispatcher +lsof -t -i:8853 | xargs kill -9 # frontend + +# Select GPUs: +export CUDA_VISIBLE_DEVICES=0,2 +rapidfireai start + +# HF login issues: login from SAME venv +source .venv/bin/activate +huggingface-cli login +``` + +## Online Aggregation Metrics Types +- **Distributive**: purely additive (count, sum) — specify `"is_distributive": True` +- **Algebraic**: averages/proportions — specify `"is_algebraic": True` +- Both require `"value_range": (min, max)` for CI calculation +- CI strategies: `"normal"` (default, CLT), `"wilson"` (small n or near 0/1), `"hoeffding"` (distribution-free, loose) diff --git a/ide-context/.cursor/rules/rapidfireai.mdc b/ide-context/.cursor/rules/rapidfireai.mdc new file mode 100644 index 00000000..7d87eaaa --- /dev/null +++ b/ide-context/.cursor/rules/rapidfireai.mdc @@ -0,0 +1,269 @@ +--- +description: RapidFire AI Python package rules — apply when working with rapidfireai imports, experiment configs, or LLM fine-tuning/eval workflows +globs: + - "**/*.py" + - "**/*.ipynb" +alwaysApply: false +--- + +# RapidFire AI — Cursor Rules + +## Package Overview +`rapidfireai` is a hyperparallelized LLM experimentation framework. It shards data into chunks/shards and rotates multiple model configs through them concurrently, enabling interactive control (stop/resume/clone) of runs in flight. + +## Environment Setup +```bash +python3 --version # Must be 3.12+ +python3 -m venv .venv +source .venv/bin/activate +pip install rapidfireai +pip uninstall -y hf-xet # Required: fixes known HF bug +rapidfireai init # SFT/RFT +rapidfireai init --evals # RAG/evals +rapidfireai start # Start server (keep terminal open) +``` +Ports: 8850 (jupyter), 8851 (dispatcher), 8852 (mlflow), 8853 (frontend/dashboard) + +## Core Architecture Pattern + +Think of RapidFire AI like a **tournament bracket for ML configs**: instead of running configs one-by-one (sequential), it interleaves all configs across data shards so you see early results for everyone simultaneously — and can cut losers early. + +``` +Experiment + └─ Config Group (multiple knob combinations) + └─ Run × N (one per leaf config) + └─ Chunks/Shards (data subdivisions for concurrent scheduling) +``` + +## Imports Reference + +```python +# Core +from rapidfireai import Experiment + +# Multi-config builders +from rapidfireai.automl import List, Range, RFGridSearch, RFRandomSearch + +# SFT/RFT +from rapidfireai.automl import RFModelConfig, RFLoraConfig, RFSFTConfig, RFDPOConfig, RFGRPOConfig + +# RAG/Evals +from rapidfireai.automl import RFLangChainRagSpec, RFvLLMModelConfig, RFOpenAIAPIModelConfig, RFPromptManager + +# IC Ops (in-notebook control panel) +from rapidfireai.fit.utils.interactive_controller import InteractiveController +``` + +## Experiment Lifecycle + +```python +# --- ALWAYS: create experiment with a unique name --- +experiment = Experiment(experiment_name="exp-v1", mode="fit") # or mode="evals" + +# --- FIT workflow (SFT / DPO / GRPO) --- +experiment.run_fit( + param_config=config_group, + create_model_fn=my_create_model, + train_dataset=train_ds, + eval_dataset=eval_ds, + num_chunks=4, # >= 4 recommended + seed=42 +) + +# --- EVAL workflow (RAG / context engineering) --- +results = experiment.run_evals( + config_group=config_group, + dataset=eval_ds, + num_shards=4, # >= 4 recommended + num_actors=8, # parallel workers; max 16 for CPU-only + seed=42 +) + +# --- ALWAYS: end when done --- +experiment.end() +``` + +## Knob Specification Rules + +```python +# List() = discrete set of values for a knob +List([16, 32, 64]) +List(["linear", "cosine"]) +List([obj1, obj2]) + +# Range() = continuous interval (RFRandomSearch only — NOT RFGridSearch) +Range(1e-5, 1e-3, dtype="float") +Range(8, 128, dtype="int") + +# RFGridSearch: cartesian product of all List() knobs (no Range allowed) +config_group = RFGridSearch(configs=my_config_dict, trainer_type="SFT") + +# RFRandomSearch: IID sampling from List() and Range() knobs +config_group = RFRandomSearch(configs=my_config_dict, trainer_type="SFT", num_runs=10) + +# trainer_type: "SFT" | "DPO" | "GRPO" | None (None for run_evals in evals mode) +``` + +## SFT Example (Full Pattern) + +```python +from rapidfireai import Experiment +from rapidfireai.automl import RFModelConfig, RFLoraConfig, RFSFTConfig, List, RFGridSearch + +def create_model(model_config: dict): + from transformers import AutoModelForCausalLM, AutoTokenizer + model = AutoModelForCausalLM.from_pretrained( + model_config["model_name"], **model_config["model_kwargs"] + ) + tokenizer = AutoTokenizer.from_pretrained(model_config["model_name"]) + return model, tokenizer + +def format_row(row: dict) -> dict: + return { + "prompt": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": row["instruction"]}, + ], + "completion": [{"role": "assistant", "content": row["response"]}] + } + +config_group = RFGridSearch( + configs={ + "model_config": RFModelConfig( + model_name=List(["meta-llama/Llama-3.1-8B-Instruct", "mistralai/Mistral-7B-Instruct-v0.3"]), + peft_config=RFLoraConfig( + r=List([16, 128]), + lora_alpha=List([32, 256]), + target_modules=["q_proj", "v_proj"], + ), + training_args=RFSFTConfig( + learning_rate=2e-4, + per_device_train_batch_size=4, + num_train_epochs=2, + eval_strategy="steps", + eval_steps=25, + fp16=True, + ), + model_kwargs={"device_map": "auto", "torch_dtype": "auto", "use_cache": False}, + formatting_func=format_row, + ) + }, + trainer_type="SFT" +) + +experiment = Experiment("sft-experiment", mode="fit") +experiment.run_fit(config_group, create_model, train_ds, eval_ds, num_chunks=4) +experiment.end() +``` + +## RAG Eval Example (Full Pattern) + +```python +from rapidfireai import Experiment +from rapidfireai.automl import ( + RFLangChainRagSpec, RFvLLMModelConfig, RFGridSearch, List +) +from langchain_community.document_loaders import DirectoryLoader +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.embeddings import HuggingFaceEmbeddings + +rag_spec = RFLangChainRagSpec( + document_loader=DirectoryLoader("data/", glob="*.jsonl"), + text_splitter=List([ + RecursiveCharacterTextSplitter(chunk_size=256, chunk_overlap=32), + RecursiveCharacterTextSplitter(chunk_size=128, chunk_overlap=32), + ]), + embedding_cls=HuggingFaceEmbeddings, + embedding_kwargs={"model_name": "sentence-transformers/all-MiniLM-L6-v2"}, + search_type="similarity", + search_kwargs={"k": 15}, +) + +def preprocess(batch, rag, prompt_manager): + context = rag.get_context(batch["query"]) + return { + "prompts": [ + [{"role": "system", "content": "Answer the question."}, + {"role": "user", "content": f"Context: {ctx}\nQ: {q}"}] + for q, ctx in zip(batch["query"], context) + ], + **batch + } + +def compute_metrics(batch): + correct = sum(p == g for p, g in zip(batch["generated_text"], batch["ground_truth"])) + return {"Correct": {"value": correct}, "Total": {"value": len(batch["query"])}} + +def accumulate_metrics(agg): + correct = sum(m["value"] for m in agg["Correct"]) + total = sum(m["value"] for m in agg["Total"]) + return { + "Correct": {"value": correct, "is_distributive": True, "value_range": (0, 1)}, + "Total": {"value": total}, + "Accuracy": {"value": correct / total, "is_algebraic": True, "value_range": (0, 1)}, + } + +config_group = RFGridSearch({ + "vllm_config": RFvLLMModelConfig( + model_config={"model": "Qwen/Qwen2.5-0.5B-Instruct", "dtype": "half", "max_model_len": 2048}, + sampling_params={"temperature": 0.8, "max_tokens": 512}, + rag=rag_spec, + ), + "batch_size": 8, + "preprocess_fn": preprocess, + "compute_metrics_fn": compute_metrics, + "accumulate_metrics_fn": accumulate_metrics, + "online_strategy_kwargs": {"strategy_name": "normal", "confidence_level": 0.95, "use_fpc": True}, +}) + +experiment = Experiment("rag-experiment", mode="evals") +results = experiment.run_evals(config_group, dataset=eval_ds, num_shards=4, num_actors=4) +experiment.end() +``` + +## Common Mistakes to Avoid + +| ❌ Wrong | ✅ Right | +|---------|---------| +| `Range()` in `RFGridSearch` | Use `List()` in `RFGridSearch`; `Range()` only in `RFRandomSearch` | +| Omitting `trainer_type` in fit mode | Always set `trainer_type="SFT"/"DPO"/"GRPO"` for `run_fit()` | +| `mode="fit"` + `run_evals()` | Match `mode` to the method: `"fit"` → `run_fit()`, `"evals"` → `run_evals()` | +| `kill -9` on rapidfireai server | Always `rapidfireai stop` or `Ctrl+C` gracefully | +| Running `rapidfireai init` every session | `init` is one-time per venv | +| Forgetting `pip uninstall -y hf-xet` | Always uninstall after `pip install rapidfireai` | +| Passing embedding instance to `embedding_cls` | Pass the **class** (e.g. `HuggingFaceEmbeddings`), not `HuggingFaceEmbeddings(...)` | +| `num_chunks=1` or `num_shards=1` | Use ≥ 4 for meaningful concurrent visibility | + +## LoRA Knob Guidance + +```python +# Start here — quick exploration: +RFLoraConfig(r=List([16, 32]), lora_alpha=List([32, 64]), target_modules=["q_proj", "v_proj"]) + +# High capacity — if underfitting: +RFLoraConfig(r=128, lora_alpha=256, target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"]) + +# Key knobs in order of impact: r (rank) > target_modules > lora_alpha > lora_dropout +``` + +## Diagnostics + +```bash +rapidfireai doctor # Full env/GPU/package check +lsof -t -i:8853 | xargs kill -9 # Free frontend port +export CUDA_VISIBLE_DEVICES=0,1 # Select GPUs before start +ps -ef | grep "multiprocessing.spawn" # Find stray processes between experiments +``` + +## Results Inspection + +```python +# After run_fit(): +runs_df = experiment.get_runs_info() # run_id, status, config, etc. +results_df = experiment.get_results() # all metrics, all steps, all runs + +# After run_evals(): +# results dict returned directly by run_evals() +for run_id, (agg_metrics, cumulative_metrics) in results.items(): + print(run_id, agg_metrics) +``` diff --git a/ide-context/CLAUDE.md b/ide-context/CLAUDE.md new file mode 100644 index 00000000..0f119467 --- /dev/null +++ b/ide-context/CLAUDE.md @@ -0,0 +1,74 @@ +# RapidFire AI Project + +## What Is This +RapidFire AI (`rapidfireai`) is a Python package for hyperparallelized LLM experimentation — running multiple configs concurrently, with real-time IC Ops (stop/resume/clone/delete runs mid-flight). + +Supports two workflows: +- **RAG / Context Engineering** — evals mode, uses `run_evals()` +- **Fine-Tuning / Post-Training** — fit mode (SFT, DPO, GRPO), uses `run_fit()` + +## Environment Requirements +- Python **3.12+** (required — verify before creating venv) +- `pip install rapidfireai` +- After install: `pip uninstall -y hf-xet` (known upstream bug) +- Ports used: **8850** (jupyter), **8851** (dispatcher), **8852** (mlflow), **8853** (frontend) + +## Server Lifecycle +```bash +rapidfireai init # Run ONCE per venv (or when switching GPUs) +rapidfireai init --evals # RAG/evals variant +rapidfireai start # Start services — leave terminal running +rapidfireai stop # Graceful stop (NEVER kill -9) +rapidfireai doctor # Diagnostics +``` + +## Core Mental Model +``` +Experiment (named, unique) + └── Config Group (grid/random search over knobs) + └── Run (one leaf config = one run) + └── Chunks/Shards (data split for concurrent execution) +``` + +## Canonical Code Pattern + +### Fit (SFT/DPO/GRPO) +```python +from rapidfireai import Experiment +from rapidfireai.automl import RFModelConfig, RFLoraConfig, RFSFTConfig, List, RFGridSearch + +experiment = Experiment(experiment_name="my-exp", mode="fit") + +config_group = RFGridSearch( + configs={ + "model_config": RFModelConfig( + model_name="meta-llama/Llama-3.1-8B-Instruct", + peft_config=RFLoraConfig(r=List([16, 128]), lora_alpha=List([32, 256])), + training_args=RFSFTConfig(learning_rate=2e-4, num_train_epochs=2), + model_kwargs={"device_map": "auto", "torch_dtype": "auto"}, + ) + }, + trainer_type="SFT" +) + +experiment.run_fit(config_group, create_model_fn, train_dataset, eval_dataset, num_chunks=4) +experiment.end() +``` + +### Eval (RAG) +```python +experiment = Experiment(experiment_name="my-rag-exp", mode="evals") +results = experiment.run_evals(config_group, dataset=eval_dataset, num_shards=4, num_actors=8) +experiment.end() +``` + +## Key Rules +- `mode="fit"` → use `run_fit()` | `mode="evals"` → use `run_evals()` +- `List([...])` wraps discrete knob values; `Range(start, end, dtype=)` for continuous +- `RFGridSearch` → no `Range()` allowed; `RFRandomSearch` → allows both `List` and `Range` +- `num_chunks` / `num_shards` ≥ 4 recommended for meaningful concurrency +- `trainer_type` argument required for `RFGridSearch`/`RFRandomSearch` in fit mode; omit for evals +- Stopping the server forcibly loses experiment artifacts — always use `rapidfireai stop` + +## Detailed API Reference +See `.claude/rules/rapidfireai-api.md` for full class signatures, all config knobs, and user-provided function contracts. diff --git a/ide-context/README.md b/ide-context/README.md new file mode 100644 index 00000000..13584283 --- /dev/null +++ b/ide-context/README.md @@ -0,0 +1,56 @@ +# RapidFire AI — IDE Context Files + +These files teach **Claude Code** and **Cursor** how to work with the `rapidfireai` Python package — covering the full API, usage patterns, common mistakes, and correct workflows for both fine-tuning and RAG experiments. + +## Install (One Command) + +**Run this from your project root** — the directory where you write your rapidfireai experiments: + +```bash +# Recommended: use the CLI you already have (works on all platforms) +rapidfireai install-ide-context +``` + +Fallback if you don't have the CLI yet: + +```bash +# macOS / Linux +curl -sSL https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context/install.sh | bash + +# Windows (PowerShell) +irm https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context/install.ps1 | iex +``` + +## What Gets Installed + +``` +your-project/ +├── CLAUDE.md ← Claude Code: loaded every session +├── .claude/ +│ └── rules/ +│ └── rapidfireai-api.md ← Claude Code: full API ref (loaded on demand) +└── .cursor/ + └── rules/ + └── rapidfireai.mdc ← Cursor: auto-applied to .py and .ipynb files +``` + +## What the AI Learns + +- **Environment setup** — Python 3.12+, `pip install rapidfireai`, `hf-xet` fix, port usage +- **Server lifecycle** — `init`, `start`, `stop`, `doctor` +- **Core mental model** — Experiment → Config Group → Run → Chunks/Shards +- **Full API** — `Experiment`, `RFGridSearch`, `RFRandomSearch`, `List()`, `Range()` +- **SFT/RFT classes** — `RFModelConfig`, `RFLoraConfig`, `RFSFTConfig`, `RFDPOConfig`, `RFGRPOConfig` +- **RAG classes** — `RFLangChainRagSpec`, `RFvLLMModelConfig`, `RFOpenAIAPIModelConfig`, `RFPromptManager` +- **User function contracts** — signatures and return types for all callbacks +- **IC Ops** — Stop, Resume, Clone-Modify, Delete semantics +- **Common mistakes** — `Range()` in GridSearch, wrong mode, `kill -9`, etc. +- **LoRA guidance** — which knobs matter and in what order + +## Updating + +When RapidFire AI releases a new version, re-run the install command to pull the latest context files: + +```bash +rapidfireai install-ide-context +``` diff --git a/ide-context/install.ps1 b/ide-context/install.ps1 new file mode 100644 index 00000000..7c099b9f --- /dev/null +++ b/ide-context/install.ps1 @@ -0,0 +1,26 @@ +$ErrorActionPreference = "Stop" + +Write-Host "" +Write-Host "RapidFire AI — IDE Context Installer" +Write-Host "======================================" +Write-Host "Installing into: $PWD" +Write-Host "Make sure you are running this from your project root." +Write-Host "" + +$RepoBase = "https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context" + +New-Item -ItemType Directory -Force -Path ".claude\rules" | Out-Null +New-Item -ItemType Directory -Force -Path ".cursor\rules" | Out-Null + +Invoke-WebRequest "$RepoBase/CLAUDE.md" -OutFile "CLAUDE.md" +Invoke-WebRequest "$RepoBase/.claude/rules/rapidfireai-api.md" -OutFile ".claude\rules\rapidfireai-api.md" +Invoke-WebRequest "$RepoBase/.cursor/rules/rapidfireai.mdc" -OutFile ".cursor\rules\rapidfireai.mdc" + +Write-Host "" +Write-Host "✅ Installed:" +Write-Host " CLAUDE.md" +Write-Host " .claude\rules\rapidfireai-api.md" +Write-Host " .cursor\rules\rapidfireai.mdc" +Write-Host "" +Write-Host "Claude Code and Cursor will now automatically pick up the RapidFire AI context." +Write-Host "Restart your IDE or Claude Code session to activate." diff --git a/ide-context/install.sh b/ide-context/install.sh new file mode 100644 index 00000000..ef99a290 --- /dev/null +++ b/ide-context/install.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e + +echo "" +echo "RapidFire AI — IDE Context Installer" +echo "======================================" +echo "Installing into: $(pwd)" +echo "Make sure you are running this from your project root." +echo "" + +REPO_BASE="https://raw.githubusercontent.com/RapidFireAI/rapidfireai/main/ide-context" + +mkdir -p .claude/rules +mkdir -p .cursor/rules + +curl -fsSL "$REPO_BASE/CLAUDE.md" -o CLAUDE.md +curl -fsSL "$REPO_BASE/.claude/rules/rapidfireai-api.md" -o .claude/rules/rapidfireai-api.md +curl -fsSL "$REPO_BASE/.cursor/rules/rapidfireai.mdc" -o .cursor/rules/rapidfireai.mdc + +echo "" +echo "✅ Installed:" +echo " CLAUDE.md" +echo " .claude/rules/rapidfireai-api.md" +echo " .cursor/rules/rapidfireai.mdc" +echo "" +echo "Claude Code and Cursor will now automatically pick up the RapidFire AI context." +echo "Restart your IDE or Claude Code session to activate." diff --git a/pyproject.toml b/pyproject.toml index 249ca8ba..e8e4759e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -298,6 +298,7 @@ rapidfireai = [ "../setup/evals/requirements-*.txt", "../tutorial_notebooks/**/*", "../tests/notebooks/*", + "../ide-context/**/*", ] diff --git a/rapidfireai/cli.py b/rapidfireai/cli.py index 0edf4c8c..e1120e8f 100644 --- a/rapidfireai/cli.py +++ b/rapidfireai/cli.py @@ -276,6 +276,65 @@ def install_packages(evals: bool = False, init_packages: list[str] | None = None return 0 +def install_ide_context(): + """Copy IDE context files (Claude Code + Cursor rules) into the current project directory.""" + dest = Path.cwd() + print("") + print("RapidFire AI — IDE Context Installer") + print("--------------------------------------") + print(f"Installing into: {dest}") + print("Run this from your project root — files will be placed in the current directory.") + print("") + + try: + site_packages_path = site.getsitepackages()[0] + source = Path(site_packages_path) / "ide-context" + + if not source.exists(): + # Fallback for editable / dev installs: look relative to this file + source = Path(__file__).parent.parent / "ide-context" + + if not source.exists(): + print("❌ Could not locate the ide-context directory.") + print(" Try reinstalling: pip install --upgrade rapidfireai") + return 1 + + files_to_copy = [ + ("CLAUDE.md", "CLAUDE.md"), + (".claude/rules/rapidfireai-api.md", ".claude/rules/rapidfireai-api.md"), + (".cursor/rules/rapidfireai.mdc", ".cursor/rules/rapidfireai.mdc"), + ] + + installed = [] + for src_rel, dest_rel in files_to_copy: + src_path = source / src_rel + dest_path = dest / dest_rel + if src_path.exists(): + dest_path.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(str(src_path), str(dest_path)) + installed.append(f" {dest_rel}") + else: + print(f"⚠️ Source file not found, skipping: {src_rel}") + + if installed: + print("✅ Installed:") + for f in installed: + print(f) + print("") + print("Claude Code and Cursor will now pick up the RapidFire AI context.") + print("Restart your IDE or Claude Code session to activate.") + else: + print("❌ No files were installed. The ide-context source may be incomplete.") + return 1 + + except Exception as e: + print(f"❌ Failed to install IDE context") + print(f" Error: {e}") + return 1 + + return 0 + + def copy_tutorial_notebooks(): """Copy the tutorial notebooks to the project.""" print("Getting tutorial notebooks...") @@ -409,6 +468,9 @@ def main(): # Stop services rapidfireai stop + # Install Claude Code + Cursor context files into your project (run from project root) + rapidfireai install-ide-context + For more information, visit: https://github.com/RapidFireAI/rapidfireai """ ) @@ -417,7 +479,7 @@ def main(): "command", nargs="?", default="start", - choices=["start", "stop", "status", "restart", "setup", "doctor", "init", "jupyter"], + choices=["start", "stop", "status", "restart", "setup", "doctor", "init", "jupyter", "install-ide-context"], help="Command to execute (default: start)", ) @@ -488,7 +550,10 @@ def main(): # Handle init command separately if args.command == "init": return run_init(args.evals) - + + if args.command == "install-ide-context": + return install_ide_context() + if args.command == "jupyter": return run_jupyter()