Instructions to use Pricile/cassava-rca-detective with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use Pricile/cassava-rca-detective with PEFT:
Task type is invalid.
- Notebooks
- Google Colab
- Kaggle
- 1st place β Cassava AI Root Cause Detective
- π Acknowledgements
- π’ Background β the honest story
- π₯ Winning solution
- π What actually moved the score [every number measured]
- π¬ The thing I'm most proud of: measuring without test labels
- π§± Dead ends (all measured, none guessed)
- π Reproducibility & compliance
- π If I had more time
- ποΈ If one idea survives this writeup
- π Acknowledgements
1st place β Cassava AI Root Cause Detective
5G root-cause diagnosis Β· β€ 4B open model Β· Solution writeup Β· 1st prize
Zindi "Cassava AI Root Cause Detective Hackathon" Β· Deep Learning Indaba 2026 Β· public 0.99 (258/259) Β· 1st prize ($1000).
π Acknowledgements
Before anything else, a heartfelt thank-you to my mentor Kevin Degila for his mentorship, his advice throughout this challenge, and for providing the compute resources that made these training runs possible. This result would not have been possible without his guidance and support. π
π’ Background β the honest story
A short bit of story, because it drove every technical decision.
I started this challenge with a strong-looking neuro-symbolic idea and reached ~0.97 public fast β by injecting the measurements my rule engine computed into the prompt and training on pseudo-labels of the test. Then, on 23 July, Zindi clarified the rules: the model must generate the answer, no rule-based computation, no testβtrain/validation lookup. My 0.97 method was now non-compliant. I had to throw it away.
What actually won this competition is what came next: rebuilding a fully compliant pipeline and then measuring, not guessing, my way up from 0.84 to 0.996. Every jump below is a measured number, and the most important tool I built was not a model it was a way to predict my public score before submitting. Below is the full method, with numbers, and the dead ends too.
π₯ Winning solution
TLDR: LoRA fine-tune of an open-source β€ 4B model (Qwen3-4B-Instruct-2507) on reasoning
traces written by my own pure-Python rule engine (no LLM in the data pipeline). At inference the
model reads the raw question and generates its own reasoning + \boxed{} answer. Questions are then
routed by family, with a small unlabelled self-consistency cascade and two more β€ 4B models arbitrating
the hardest general-knowledge MCQs.
- Base model:
Qwen/Qwen3-4B-Instruct-2507(open weights, Apache-2.0, run locally). Fits the β€ 4B rule (declared honestly: 4,022,468,096 total params, 3.63 B excluding tied embeddings). - Training data β the compliant core: the test splits into 3 families β 681 telelogs "C" questions
(8 causes), 100 signalling "A-I" questions (9 causes), 82 general-knowledge MCQs. I built the SFT
reasoning traces with two hand-written rule engines (
rule_solver.py,ai_solver.py, verified 2400/2400 on train and 864/864 on validation), from the provided data only + synthetic A-I clones I generate myself. No language model of any size touches the data pipeline β a real advantage, since the reference paper distils traces from 32B models, which the rules later disallowed. - The key training idea β "decisive" traces: my first traces marked several candidate causes as
SUPPORTED, and the model learned to hesitate. I forced exactly one cause SUPPORTED per trace (the true root cause; near-misses explicitly ruled out). C-family agreement jumped 0.82 β 0.956. - Fine-tuning: LoRA r64, 2 epochs, lr 1.5e-4, effective batch 16, bf16, gradient checkpointing, seed 42, on 1Γ NVIDIA L40S (48 GB).
- Inference: greedy, deterministic, raw question in β
\boxed{}out. No measurement/threshold in the prompt, no rule engine at inference. - Assembly (
build_submission_v6.py): route by family; unlabelled self-consistency repairs (hesitation fallback + arithmetic detectors); MCQ cascade. It reads onlytest.csv+SampleSubmission.csv+ model generations β no label file.
π What actually moved the score [every number measured]
| Step | Lever | Public |
|---|---|---|
| Compliant baseline | ambiguous traces, single model | 0.8378 |
| Decisive traces + family routing | one SUPPORTED per trace; Cβdecisive model, A-Iβother model | 0.9537 |
| Hesitation fallback | if the model marks β 1 SUPPORTED, take another model's generation (+12 questions) | 0.9691 |
| Arithmetic self-consistency | reject generations whose own numbers (mod-30, thresholds, downtilt) contradict the verdict; repair from a model retrained to state them correctly | 0.9846 |
| MCQ cascade | self-consistency vote β prompt-ensemble β un-fine-tuned base model arbiter β Qwen3-4B-Thinking-2507 reasoning arbiter on the hardest contest problems | 0.9884 |
| Final MCQ reasoning + selection | reasoning model resolves the last undecided MCQs; two finals bracket the one publicly-unmeasured question | 0.9961 |
- Decisive traces β the biggest single lever. The rule engine solves the C-family at 100 %, so the answer is in the question; a model stuck at 0.82 was executing the reasoning sloppily, not missing information. Teaching it to commit to exactly one cause moved C from 0.82 β 0.956. Error analysis proved it: when the model marks 1 SUPPORTED it is 95.4 % right; with β₯ 2, ~60 %.
- Routing, not a single model. Folding decisive-C and A-I into one model broke A-I badly (dilution). Routing Cβdecisive model and A-Iβthe other model kept both (C 0.956 + A-I 0.96) β compliant, since routing is by question format, exactly like general-MCQβbase.
- Detectors that read the model, not the data. The model sometimes asserts a false congruence ("PCI 745 vs 980: both mod 30 = 20", while 745 % 30 = 25). A detector that checks the arithmetic the model itself wrote fired 18 times, 18 wrong, 0 correct broken. Root cause was in my training corpus (no negative example of "moduli differ") β fixed by patching the trace generator, not the model.
- The MCQ contest problems needed a reasoning model. The base model returned 0 readable answer in 32
draws on some problems β not ignorance, truncation (it never reached
\boxed{}in 1200 tokens). At β₯ 4096 tokens,Qwen3-4B-Thinking-2507finishes them.
π¬ The thing I'm most proud of: measuring without test labels
With no test transcripts, I built a test-proxy: run inference on test.csv, score the agreement with
my rule engine's predictions (oracle_test.csv + ai_predictions.csv). It predicted the public
leaderboard to Β±0.001 every time (0.837 test-proxy β 0.837838 public; 0.956 test-proxy β 0.953667
public). That let me iterate for days without spending a single submission, and select models on a
faithful signal instead of leaderboard feedback.
A hierarchy of measurement instruments, by trust: (1) the rule-engine test-proxy on the measurable families; (2) the model's own self-consistency (SUPPORTED count, arithmetic checks); (3) the public leaderboard (costs a slot, and only 259/863 questions). Most mistakes came from trusting a lower rung than I should have.
π§± Dead ends (all measured, none guessed)
- Single unified model (decisive C + rebalanced A-I). Retrained with A-I at 12 % of the mix; A-I still collapsed to 0.10 and C regressed to 0.87. Routing two models beats one here. Kept the routing.
- k = 8 self-consistency on the decisive model. Test-proxy +0.15 pt on C, but public went down (247β245/259). At the ceiling, this is noise. Kept greedy.
- The 3 last measurable errors (2 C, 1 A-I where the model disagrees with the rule engine). My fine-tuned model, the base model and the reasoning model each give a different answer β genuinely ambiguous. No compliant way to fix them, so I left them.
- Evidence injection (the old 0.97). Banned by the 23 July clarification. Abandoned entirely and documented as non-compliant.
π Reproducibility & compliance
- Byte-exact reproduction, no GPU. The model generations are frozen in the package;
verify_reproduction.pyextracts it, adds onlytest.csv+SampleSubmission.csv, replays the assembly, and confirms the two selected files are regenerated byte-for-byte in under a minute. - Compliance, rule by rule (
CERTIFICAT_CONFORMITE.md): β€ 4B open model; every answer generated by the model from the raw question; no rule engine at inference; no test lookup; provided data only; no LLM in the data pipeline. I also self-declare one defect I found and fixed (2 of 863 answers had been written by a format guardrail rather than the model β regenerated by sampling). - Fixed seeds, pinned
requirements.txt, documentation following the Zindi guidelines (SOLUTION_DOCUMENTATION.md).
π If I had more time
- Train longer (the schedule was short) and try syllable/BPE tokenisation for the reasoning traces.
- A cleaner single model for A-I, so routing isn't needed.
- A larger clean dev to tune MCQ decoding at higher statistical power.
ποΈ If one idea survives this writeup
Measure, don't guess β and audit your own training data. My biggest jump (decisive traces) came from looking at what my traces were teaching the model, not from a bigger model or more data. And the test-proxy that predicted the public score to Β±0.001 is what made every other decision safe.
Author: Pricile GANGBE (Zindi: Pricilegangbe). Everything Apache-2.0 / open. Code, models and the reproducible review package are in this repository.
- Downloads last month
- -
Model tree for Pricile/cassava-rca-detective
Base model
Qwen/Qwen3-4B-Instruct-2507