[{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/azerbaijani/","section":"Tags","summary":"","title":"Azerbaijani","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/fastapi/","section":"Tags","summary":"","title":"FastAPI","type":"tags"},{"content":"Status: Complete · Nov 2025 – Jan 2026 · GitHub\nTL;DR # I built a sentence boundary detector specialized for Azerbaijani legal text, not general-purpose prose. It is a random forest classifier that decides, for every candidate terminator character (., !, ?, ;, quotes, newlines…), whether a sentence actually ends there. Each candidate is described by a 19-character context window plus 9 engineered linguistic and punctuation features. The model reaches 0.958 F1 on boundary detection.\nFor serving, I compiled the forest with Treelite and deployed it on NVIDIA Triton Inference Server (FIL backend) behind a FastAPI service. Inference latency dropped from 76ms to 3ms, a 25× speedup.\nProblem: Why Legal Text Breaks Sentence Splitters # Sentence splitting is the first step of almost every NLP pipeline (chunking for retrieval, summarization, translation), and errors here propagate everywhere downstream. Legal documents are the hardest case: laws, regulations, court decisions and contracts are dense with abbreviations, numbered clauses, nested lists and citations. Splitting on . breaks immediately on Azerbaijani legal text:\nAbbreviations: prof., məs., mln., kv.m., Ph.D., initials like Ə.M., Roman numerals like XIV. Enumerations: 1., a), (iii), bullet symbols, and lists introduced by legal formulas like \u0026ldquo;aşağıdakılar müəyyən edilir:\u0026rdquo; (\u0026ldquo;the following are established:\u0026rdquo;) or \u0026ldquo;tərəflər aşağıdakılar barədə razıdır:\u0026rdquo; (\u0026ldquo;the parties agree on the following:\u0026rdquo;) Quotes: a closing quote may or may not end the sentence, depending on balance and what follows Semicolons and colons: they end clauses inside lists, but sometimes end sentences General-purpose splitters are tuned for English, everyday prose and Wikipedia-style text. They miss both Azerbaijani-specific abbreviations and the structure of legal documents. Neural models would handle this, but they are overkill in cost and latency for a step that runs on every document.\nApproach: Character-Level Classification # The design follows the structure of the CharBoundary library, adapted for Azerbaijani.\n1. Candidate detection. Only characters that could end a sentence are classified. Everything else is skipped, so the model sees a small fraction of the text.\n2. Character encoding. Each character is mapped to an integer by type: Azerbaijani letters (all 33, including ə, ğ, ı, ş, ç, ö, ü) get their own IDs; digits, terminators, whitespace, punctuation and other characters get category codes.\n3. Feature vector (28 dims) for each candidate position:\nFeatures Dims Description Context window 19 Encoded characters, 9 to the left + the candidate + 9 to the right Abbreviation flag 1 Token ending at this period is in an 830+ entry Azerbaijani abbreviation list Terminator type 1 +1 primary (. ! ? newline), −1 secondary (quotes, ;, :), 0 otherwise Quote balance 1 Are straight and curly quotes balanced up to this point? Word completion 1 Does a closing quote look like it ends a word? Followed by lowercase 1 Next character is lowercase, a strong \u0026ldquo;not a boundary\u0026rdquo; signal In list item 1 Near a list marker (1., a), (ii), •…) or list conjunction (və, və ya, həmçinin…) Semicolon in list 1 ; separating list items rather than sentences Near colon 1 Proximity to a list-introducing colon Next word uppercase 1 Next word starts with a capital letter 4. Classifier. A random forest (128 trees, max depth 32) trained on labeled Azerbaijani text. Training and evaluation use a 0.75 probability threshold, which favors precision: a missed split is cheaper downstream than a wrong one.\n5. Preprocessing. Training text is normalized first: Cyrillic and Greek look-alike characters are mapped to Latin (а → a, ә → ə), and chunks with characters outside the Azerbaijani alphabet are dropped.\nServing: Triton FIL # The random forest in scikit-learn was the latency bottleneck at 76ms. The fix was to stop running it in Python:\nTreelite compilation. The trained forest is converted to a Treelite checkpoint. Triton FIL backend. Triton\u0026rsquo;s Forest Inference Library runs tree ensembles natively on NVIDIA GPUs (or CPU) and returns class probabilities. Dynamic batching. Triton merges concurrent requests into batches of up to 1,024 candidates, with a 5ms max queue delay. FastAPI front end. The API extracts features, calls Triton over gRPC, and splits the text at positive predictions. Result: 76ms → 3ms (25×).\nThe whole stack runs under Docker Compose: Triton plus the API, with health checks that separate liveness (/health) from readiness (/ready, meaning Triton is reachable and the model is loaded).\ncurl -X POST http://localhost:8000/segment \\ -H \u0026#39;Content-Type: application/json\u0026#39; \\ -d \u0026#39;{\u0026#34;text\u0026#34;: \u0026#34;Bu birinci cümlədir. Bu isə ikinci cümlədir.\u0026#34;}\u0026#39; Results # Metric Value Boundary F1 0.958 Latency (scikit-learn) 76ms Latency (Triton FIL) 3ms Speedup 25× Feature vector 28 dims Model Random forest, 128 trees, depth 32 Team # Built together with Turan Abbasli and Rashad Mammadov.\nStack # Python · scikit-learn · NumPy · Treelite · NVIDIA Triton Inference Server (FIL) · gRPC · FastAPI · Docker Compose\nCode on GitHub.\n","date":"31 January 2026","externalUrl":null,"permalink":"/projects/sentence-boundary-detection/","section":"Projects","summary":"Status: Complete · Nov 2025 – Jan 2026 · GitHub\nTL;DR # I built a sentence boundary detector specialized for Azerbaijani legal text, not general-purpose prose. It is a random forest classifier that decides, for every candidate terminator character (., !, ?, ;, quotes, newlines…), whether a sentence actually ends there. Each candidate is described by a 19-character context window plus 9 engineered linguistic and punctuation features. The model reaches 0.958 F1 on boundary detection.\n","title":"Legal Sentence Boundary Detection for Azerbaijani: 76ms → 3ms with Triton FIL","type":"projects"},{"content":"Based in Baltimore, MD. MSE in Computer Science at Johns Hopkins and Graduate Course Assistant for Artificial Intelligence. Previously built E-QANUN.AI, Azerbaijan\u0026rsquo;s national legal search platform, at the National AI Center.\n","date":"31 January 2026","externalUrl":null,"permalink":"/","section":"Murad Baghirli","summary":"Based in Baltimore, MD. MSE in Computer Science at Johns Hopkins and Graduate Course Assistant for Artificial Intelligence. Previously built E-QANUN.AI, Azerbaijan’s national legal search platform, at the National AI Center.\n","title":"Murad Baghirli","type":"page"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/nlp/","section":"Tags","summary":"","title":"NLP","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/nvidia-triton/","section":"Tags","summary":"","title":"NVIDIA Triton","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/random-forest/","section":"Tags","summary":"","title":"Random Forest","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/scikit-learn/","section":"Tags","summary":"","title":"Scikit-Learn","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"31 January 2026","externalUrl":null,"permalink":"/tags/treelite/","section":"Tags","summary":"","title":"Treelite","type":"tags"},{"content":"Status: In training · Phase 1 active on 8× NVIDIA H200 · Component 2 of the AzBERT pipeline\nTL;DR # I am training AzNEOBERT — an Azerbaijani encoder language model from scratch, based on the NeoBERT architecture. The model runs on 8× NVIDIA H200 GPUs via SLURM, trained on ~12.2B tokens of Azerbaijani text across 11 corpus collections. Infrastructure stack: DeepSpeed ZeRO-2, Flash Attention 3, and torch.compile, reaching 1.24M tokens/sec throughput. Loss dropped from 11.1 (random init) to 2.35 within the first 4,100 steps.\nThis is the second component of the AzBERT pipeline — built directly on top of the Azerbaijani tokenizer.\nProblem — Why Train from Scratch # No dedicated Azerbaijani encoder model exists with modern architecture. The practical options are multilingual models (mBERT, XLM-R) or community BERT variants trained on limited data with outdated architectures.\nThe cost of relying on multilingual models is documented: mBERT tokenizes Azerbaijani at 3.7 fertility — nearly 4 subword pieces per word on average. This wastes sequence length and degrades attention locality on an agglutinative language where a single word often carries clause-level meaning. Beyond tokenization, multilingual models share capacity across 100+ languages, leaving Azerbaijani morphology and syntax under-represented at every layer.\nTraining from scratch with a language-specific tokenizer (64k vocab, 1.67 fertility) and a modern encoder architecture is the only path to a model that understands Azerbaijani natively.\nArchitecture # AzNEOBERT follows the NeoBERT design — a modernized BERT replacing every outdated component with its current best-practice equivalent.\nParameter Value Hidden size 768 Layers 28 Attention heads 12 Head dimension 64 FFN intermediate 3,072 Max sequence length 1,024 (Phase 1) Positional encoding RoPE Normalization Pre-RMSNorm (ε = 1e-5) Activation SwiGLU Bias None Dropout None Parameters ~160M What changed vs. original BERT # RoPE instead of learned absolute positions. Rotary Position Embeddings encode relative position directly into the attention computation, generalizing better to sequence lengths unseen during training. This matters for Phase 2, where the sequence length extends to 4,096.\nPre-RMSNorm instead of Post-LayerNorm. Normalizing before each sub-layer (rather than after) stabilizes training at scale — gradient magnitude stays bounded without requiring careful initialization. RMSNorm drops the mean-centering term from LayerNorm, reducing compute with no quality loss.\nSwiGLU instead of GELU FFN. The gated linear unit variant replaces the two-matrix FFN with a three-matrix gated structure, consistently outperforming GELU on language modeling benchmarks at matched parameter count.\nNo bias, no dropout. Both are removed following modern large-model practice. Bias terms add parameters without expressivity; dropout at pretraining scale degrades rather than regularizes.\nAttention implementation # # Fused QKV projection + RoPE + Flash Attention 3 projection = self.qkv(x) # single fused projection xq, xk, xv = projection.chunk(3, dim=-1) xq, xk = apply_rotary_emb(xq, xk, freqs_cis) # LLaMA-style RoPE out = flash_attn_func(xq, xk, xv, dropout_p=0.0) # Flash Attention 3 Dataset # Collection Documents Source Type hplt3 2,000,000 Filtered web crawl fineweb2 1,500,000 Curated web court_cases 1,500,000 Legal text cc100 1,300,000 Web crawl culturax 1,200,000 mC4 + OSCAR pdfs 1,200,000 PDF extractions nllb 1,000,000 Sentence-level parallel news 1,100,000 News articles e-qanun 531,000 Legislative text folklor 338,000 Dialectal / folkloric wikipedia — Encyclopedic Total: ~11.9M sequences × 1,024 tokens = ~12.2B tokens\nData is stored as memory-mapped numpy arrays (int32 token IDs + int64 offsets per collection) and loaded with mmap_mode='r' — the full dataset never enters RAM. A flat shuffle across all 11 collections feeds a cycling iterator; the dataset repeats ~25 times over 127,000 training steps, equivalent to ~300B token passes.\nTraining Recipe # Phase 1 # Hyperparameter Value Sequence length 1,024 Global batch size 2,304 Per-device batch size 48 Gradient accumulation 6 steps Total steps 127,000 (~300B tokens) Warmup 2,000 steps Peak learning rate 1e-4 LR schedule Linear warmup → cosine decay to 0 AdamW β₁, β₂ 0.9, 0.95 Weight decay 0.01 Gradient clipping 1.0 Mixed precision bf16 MLM mask rate 20%, 100% masked with [MASK] Weight decay is applied only to weight matrices (ndim ≥ 2); norm weights and bias-equivalent vectors are excluded.\nPhase 2 (pending) # Sequence length extends to 4,096 with a reduced global batch size of 512 for 7,000 additional steps. RoPE handles the context extension without positional extrapolation issues.\nInfrastructure # Cluster: Single node, 8× NVIDIA H200 (141 GB VRAM each) — 1.1 TB total GPU memory\nScheduler: SLURM with Apptainer containers\nDistributed: HuggingFace Accelerate + DeepSpeed ZeRO-2\nOptimizations # Optimization Effect torch.compile ~37% faster per step DeepSpeed ZeRO-2 ~10% faster, partitions optimizer state across GPUs Flash Attention 3 ~4% faster at seq_len=1024; major gains expected at 4096 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True Reduces memory fragmentation on H200 TF32 enabled Marginal gain at bf16-dominant workload persistent_workers=True Eliminates dataloader respawn overhead Throughput: ~1.24M tokens/sec across 8× H200\nStep time: ~1.68 seconds per optimizer step\nEstimated Phase 1 duration: ~60 hours\nThe torch.compile gain of 37% is notable given the RoPE complex operation warning — the compiler still optimizes effectively despite the warning, which reflects a graph break rather than a correctness issue.\nLoss Curve # Step Loss Note 0 11.10 Random init — ln(64,000) 200 7.95 Early rapid descent 2,456 4.06 Warmup complete, lr = 1e-4 4,131 2.35 Stable training phase The initial loss of 11.1 matches the theoretical random prediction baseline for a 64k-token vocabulary (ln(64000) ≈ 11.07). The drop to 2.35 by step 4,131 — within the first 3% of training — confirms the architecture and data pipeline are functioning correctly.\nStatus # Phase 1: in training. A checkpoint compatibility issue between non-DeepSpeed and ZeRO-2 checkpoint formats requires resolution before resuming from the latest saved state.\nPhase 2: pending. Sequence length extension to 4,096 after Phase 1 completes.\nStack # Python · PyTorch · DeepSpeed ZeRO-2 · HuggingFace Accelerate · Flash Attention 3 · torch.compile · SLURM · Apptainer · wandb · numpy mmap\nComponent 2 of the AzBERT pretraining pipeline. Built on the Azerbaijani tokenizer.\n","date":"1 January 2026","externalUrl":null,"permalink":"/projects/az-neobert/","section":"Projects","summary":"Status: In training · Phase 1 active on 8× NVIDIA H200 · Component 2 of the AzBERT pipeline\nTL;DR # I am training AzNEOBERT — an Azerbaijani encoder language model from scratch, based on the NeoBERT architecture. The model runs on 8× NVIDIA H200 GPUs via SLURM, trained on ~12.2B tokens of Azerbaijani text across 11 corpus collections. Infrastructure stack: DeepSpeed ZeRO-2, Flash Attention 3, and torch.compile, reaching 1.24M tokens/sec throughput. Loss dropped from 11.1 (random init) to 2.35 within the first 4,100 steps.\n","title":"AzNEOBERT — Azerbaijani BERT from Scratch on 12B Tokens","type":"projects"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/deepspeed/","section":"Tags","summary":"","title":"DeepSpeed","type":"tags"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/flash-attention/","section":"Tags","summary":"","title":"Flash Attention","type":"tags"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/huggingface-accelerate/","section":"Tags","summary":"","title":"HuggingFace Accelerate","type":"tags"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/pretraining/","section":"Tags","summary":"","title":"Pretraining","type":"tags"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/pytorch/","section":"Tags","summary":"","title":"PyTorch","type":"tags"},{"content":"","date":"1 January 2026","externalUrl":null,"permalink":"/tags/slurm/","section":"Tags","summary":"","title":"SLURM","type":"tags"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/distilbert/","section":"Tags","summary":"","title":"DistilBERT","type":"tags"},{"content":"Status: Live in production · 25,000 users · ~1,000 queries/day · NAIC\nTL;DR # I built the ML search pipeline for Azerbaijan\u0026rsquo;s national AI legal search platform at NAIC (National AI Center), part of the country\u0026rsquo;s AI Strategy 2025–2028. The system serves 25k users and ~1k queries/day across 500k+ legal documents, returning ranked results in under 200ms end-to-end.\nLive: e-qanun.ai — Research: research.e-qanun.ai\nProblem # Legal information retrieval in Azerbaijan was effectively broken. Citizens, lawyers, and judges all used the same keyword search over the national legal corpus — and it failed most of them, for different reasons.\nA citizen types \u0026ldquo;ev sahibim pulu vermir\u0026rdquo; (my landlord won\u0026rsquo;t pay) and gets nothing, because no statute contains those words. A junior lawyer searches \u0026ldquo;kirayə təmirini kim ödəyir\u0026rdquo; (who pays for rental repairs) and gets partial matches but misses cross-referenced articles. An expert searches \u0026ldquo;Maddə 680.2-yə əsasən\u0026hellip;\u0026rdquo; and the system can\u0026rsquo;t even match the query because of Unicode encoding inconsistencies in the Azerbaijani character ə (U+0259 Latin Extended-B vs. U+04D9 Cyrillic — visually identical, byte-level different).\nThe core challenge: build a retrieval system that works across all user types, handles a morphologically rich and under-resourced language, resolves encoding ambiguities, and returns legally correct results — fast enough that users don\u0026rsquo;t abandon it.\nArchitecture Overview # The pipeline has six stages, all executed within a 200ms latency budget:\nStage 1 — Query Normalization (0–20ms) Unicode normalization to resolve Latin Extended-B / Cyrillic conflicts in Azerbaijani characters. Script detection and character-level canonicalization. This is not optional — without it, the same word encoded differently produces zero matches.\nStage 2 — Spell Correction (20–40ms) Azerbaijani-specific error correction handling three error classes: keyboard proximity errors (QWERTY layout, e.g., ə → a), phonetic substitutions (müqavilə → mugavile, məhkəmə → mehkeme), and missing diacritics (müqavilə → muqavile). The correction pipeline applies edit distance, keyboard-neighbor lookup, phonetic matching, and morphological rule application in sequence.\nStage 3 — Query Understanding (40–60ms) A quantized DistilBERT model handles two tasks in parallel:\nNER: Extracts article references (Maddə 680), law names (Mülki Məcəllə), and legal concepts from the query Intent classification: Categorizes the query type (obligation question, rights inquiry, procedural lookup, specific article retrieval) to adjust downstream retrieval weights Stage 4 — Hybrid Retrieval (60–140ms) Two retrieval paths run in parallel:\nLexical: BM25 via Elasticsearch for exact-match and rare-term recall. Critical for article numbers, proper nouns, and legal terminology that dense models underweight. Semantic: Fine-tuned multilingual SBERT model serving dense vectors through Vespa. Vespa also runs its own BM25 for hybrid scoring internally. Scores are combined via weighted linear combination. The weights are adjusted dynamically based on Stage 3\u0026rsquo;s intent classification — article-reference queries shift weight toward BM25, conceptual queries toward dense retrieval.\nStage 5 — Cross-Encoder Re-ranking (140–170ms) Top 100 candidates from Stage 4 are re-scored by a cross-encoder. Unlike the bi-encoder used in retrieval (which encodes query and document separately), the cross-encoder processes the query-document pair jointly, enabling deep token-level interaction scoring that catches relevance signals the first stage misses.\nStage 6 — Result Assembly (170–200ms) Final ranking incorporates legal-hierarchy-aware signals: chapter → article → clause positioning, inter-article reference chains (e.g., Article 680.2 referencing Articles 681 and 699), and version history to surface the current-law version. Results are segmented and highlighted for the UI.\nTechnical Decisions \u0026amp; Trade-offs # Vespa + Elasticsearch instead of a single engine # I run BM25 in Elasticsearch and dense retrieval in Vespa, rather than consolidating into one system. Elasticsearch gives us battle-tested BM25 with mature Azerbaijani text analysis (custom analyzers, stemming). Vespa handles dense ANN search and also provides its own BM25 for internal hybrid scoring. The trade-off is operational complexity — two search clusters to maintain — but the alternative was worse: Elasticsearch\u0026rsquo;s kNN was immature when we started, and running BM25 only in Vespa would have meant rewriting our existing text analysis pipeline.\nWeighted linear combination over RRF for score fusion # Reciprocal Rank Fusion is simpler and parameter-free, but it discards score magnitude. For legal search, magnitude matters: a BM25 exact match on \u0026ldquo;Maddə 680\u0026rdquo; should dominate over a semantically similar but wrong article. Weighted linear combination preserves this signal, and the weights adapt per-query based on intent classification. The trade-off is that it requires careful weight tuning and the intent classifier adds a failure mode — if classification is wrong, the fusion weights are wrong.\nDynamic quantization over static for DistilBERT # I applied dynamic INT8 quantization via ONNX Runtime rather than static quantization with calibration data. Dynamic quantization gave us 39% model size reduction with minimal accuracy loss and no calibration data dependency. Static quantization could have pushed compression further, but it requires a representative calibration dataset — hard to define for legal queries that span casual language to precise article citations. The latency improvement from dynamic was sufficient to keep us within the 200ms budget.\nCross-encoder on top 100, not top 20 # Re-ranking 100 candidates instead of 20 is expensive, but legal search has a long-tail relevance problem: the correct article is often ranked 30th–80th by first-stage retrieval because the query uses colloquial language while the statute uses formal legal terminology. Cutting at top-20 would miss these. The trade-off is higher latency per query, but the accuracy gain justified it — users need the right article, not a fast wrong answer.\nFine-tuned multilingual SBERT over training from scratch # Azerbaijani is low-resource (~10M speakers, limited NLP tooling). Training an embedding model from scratch would require a large parallel corpus we didn\u0026rsquo;t have. Fine-tuning a multilingual SBERT model on our 500k+ legal document corpus gave us strong domain adaptation while leveraging cross-lingual transfer from Turkish (linguistically close) and English legal concepts. The trade-off: the base model\u0026rsquo;s tokenizer isn\u0026rsquo;t optimal for Azerbaijani morphology, which limits performance on highly agglutinative legal terms.\nResults # Metric Value MRR@10 0.85 NDCG@5 0.85 End-to-end P95 latency \u0026lt; 200ms Model size reduction (ONNX INT8) 39% Corpus size 500k+ legal documents Daily query volume ~1,000 queries/day Active users 25,000 Re-ranking candidate pool Top 100 User testing confirmed 85% accuracy across all four user categories: non-lawyers using colloquial language, junior lawyers with semi-structured queries, experienced lawyers with lazy/abbreviated queries, and experts citing specific articles.\nWhat I\u0026rsquo;d Do Differently # Invest in evaluation infrastructure earlier. We built the retrieval system before having a robust offline evaluation pipeline. This meant tuning fusion weights and re-ranking thresholds based on user testing sessions rather than automated metrics on a held-out test set. In retrospect, spending the first month building a proper evaluation harness with stratified test queries across all four user types would have accelerated every subsequent iteration.\nColBERT-style late interaction instead of cross-encoder for re-ranking. The cross-encoder produces the best relevance scores, but it\u0026rsquo;s the latency bottleneck — scoring 100 pairs sequentially is inherently slow. ColBERT\u0026rsquo;s precomputed token-level embeddings with MaxSim scoring would have given us most of the re-ranking quality at a fraction of the latency, and freed up millisecond budget for deeper structural analysis. This is the first thing I\u0026rsquo;d explore in a v2.\nTeam \u0026amp; My Role # 5–6 person AI team at NAIC. I owned the core ML subsystems: the retrieval pipeline (hybrid search, score fusion), model optimization (ONNX export, quantization), and the NER/intent classification components. System architecture decisions were collaborative, but I drove the technical choices documented above.\nStack # Python · PyTorch · ONNX Runtime · Elasticsearch · Vespa · FastAPI · DistilBERT · SBERT (multilingual) · Docker\nPart of Azerbaijan\u0026rsquo;s AI Strategy 2025–2028. Launched September 2025. © 2026 NAIC — All rights reserved.\n","date":"25 September 2025","externalUrl":null,"permalink":"/projects/eqanun/","section":"Projects","summary":"Status: Live in production · 25,000 users · ~1,000 queries/day · NAIC\nTL;DR # I built the ML search pipeline for Azerbaijan’s national AI legal search platform at NAIC (National AI Center), part of the country’s AI Strategy 2025–2028. The system serves 25k users and ~1k queries/day across 500k+ legal documents, returning ranked results in under 200ms end-to-end.\n","title":"e-qanun.ai - AI-Powered Legal Search for Azerbaijani Law","type":"projects"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/elasticsearch/","section":"Tags","summary":"","title":"Elasticsearch","type":"tags"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/faiss/","section":"Tags","summary":"","title":"FAISS","type":"tags"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/onnx/","section":"Tags","summary":"","title":"ONNX","type":"tags"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/sbert/","section":"Tags","summary":"","title":"SBERT","type":"tags"},{"content":"","date":"25 September 2025","externalUrl":null,"permalink":"/tags/vespa/","section":"Tags","summary":"","title":"Vespa","type":"tags"},{"content":"I\u0026rsquo;m an ML Engineer and an MSE student in Computer Science at Johns Hopkins University, where I\u0026rsquo;m also a Graduate Course Assistant for Artificial Intelligence (EN.601.464).\nBefore Hopkins, I spent nearly three years at Azerbaijan\u0026rsquo;s National Artificial Intelligence Center (AZBA). There I built E-QANUN.AI, the national legal search platform: 50M+ vectors across Elasticsearch and Vespa, serving 7K+ monthly active users and 50K+ queries per month. I worked across the whole stack, from training retrievers and re-rankers to designing the RAG pipeline and fine-tuning LLMs on H200 clusters.\nI also pretrained AzNeoBERT, the first modern Azerbaijani-only encoder model, on a 12.2B-token corpus.\nConnect on LinkedIn.\nWhat I Build # Hybrid search combining SBERT-LaBSE embeddings with lexical methods (BM25, fuzzy matching, n-gram indexing), with fusion weights tuned via Optuna: +24% precision. Multistage RAG pipelines: query normalization → spell correction → intent detection → hybrid retrieval → mGTE re-ranking → generation, at 250ms query-to-retrieval latency. Dense retrievers (SBERT, DPR, mGTE) trained with hard negatives, MNRL, and LambdaLoss, reaching nDCG@10 of 0.878 and MRR@10 of 0.810. LLM fine-tuning with LoRA, including Qwen3.6-35B-A3B (MoE) for Azerbaijani document summarization on 6×H200. Encoder pretraining at scale with DeepSpeed ZeRO-2, Accelerate, and SLURM on 8×H200, reaching 1.24M tokens/sec. Low-latency inference: Triton Inference Server deployments with up to 25× speedups, and services sustaining 500 RPS at 180ms p95. Core Stack # Area Tools Languages Python · C · C++ · Java · SQL ML / AI PyTorch · scikit-learn · Transformers · LLMs \u0026amp; VLMs · RAG · Fine-tuning · Pre-training · Recommender Systems Training LoRA/QLoRA · PEFT · Axolotl · Unsloth · DeepSpeed · HuggingFace Accelerate · FlashAttention · Optuna Inference \u0026amp; Optimization vLLM · NVIDIA Triton Inference Server · HuggingFace TEI · ONNX · Quantization Search / IR Vespa · Elasticsearch · HNSW · BM25 · Learning-to-Rank · Hybrid Search MLOps \u0026amp; Infra MLflow · Weights \u0026amp; Biases · Docker · Git · FastAPI · SLURM · PostgreSQL · MongoDB Education # M.S.E. in Computer Science: The Johns Hopkins University, Baltimore, MD Aug 2025 – May 2027 · GPA: 3.76/4.00\nB.Sc. in Computer Science: French-Azerbaijani University (UFAZ), Baku, Azerbaijan Sep 2021 – Jul 2025 · GPA: 3.92/4.00\n","date":"1 January 2025","externalUrl":null,"permalink":"/about/","section":"Murad Baghirli","summary":"I’m an ML Engineer and an MSE student in Computer Science at Johns Hopkins University, where I’m also a Graduate Course Assistant for Artificial Intelligence (EN.601.464).\nBefore Hopkins, I spent nearly three years at Azerbaijan’s National Artificial Intelligence Center (AZBA). There I built E-QANUN.AI, the national legal search platform: 50M+ vectors across Elasticsearch and Vespa, serving 7K+ monthly active users and 50K+ queries per month. I worked across the whole stack, from training retrievers and re-rankers to designing the RAG pipeline and fine-tuning LLMs on H200 clusters.\n","title":"About Me","type":"page"},{"content":"Open to ML engineering roles, research collaborations, and interesting NLP/retrieval problems. Best way to reach me is email.\nmbaghir1@jh.edu linkedin.com/in/muradbaghirli github.com/muradbaghirli ","date":"1 January 2025","externalUrl":null,"permalink":"/contact/","section":"Murad Baghirli","summary":"Open to ML engineering roles, research collaborations, and interesting NLP/retrieval problems. Best way to reach me is email.\nmbaghir1@jh.edu linkedin.com/in/muradbaghirli github.com/muradbaghirli ","title":"Contact","type":"page"},{"content":" Graduate Course Assistant # Johns Hopkins University · Baltimore, MD, USA Sep 2026 – Present\nSupport 136 students in Artificial Intelligence (EN.601.464) through weekly office hours and grading. Contribute to course design, including homework and project development. Machine Learning Engineer # National Artificial Intelligence Center (AZBA) · Baku, Azerbaijan Jul 2025 – Aug 2026\nBuilt E-QANUN.AI, the national legal search platform: 50M+ vectors across Elasticsearch and Vespa, 85% latency reduction via HNSW indexing, serving 7K+ monthly active users and 50K+ queries per month. Architected a multistage RAG pipeline (query normalization → spell correction → intent detection → hybrid retrieval → mGTE re-ranking → decoder generation), achieving 250ms query-to-retrieval latency. Designed hybrid search combining SBERT-LaBSE embeddings with lexical methods (BM25, fuzzy matching, n-gram indexing); tuned fusion weights with Optuna on synthetic queries, improving precision by 24%. Fine-tuned Qwen3.6-35B-A3B (MoE) with LoRA for Azerbaijani document summarization on 6×H200, adapting attention and shared-expert FFN (42M params) with frozen vision; cut eval loss from 0.561 to 0.232. Junior Machine Learning Engineer # National Artificial Intelligence Center (AZBA) · Baku, Azerbaijan Dec 2023 – Jun 2025\nTrained and evaluated SBERT/DPR/mGTE retrievers on A100/H100/H200 clusters using hard negatives, MNRL, and LambdaLoss, reaching nDCG@10 of 0.878 and MRR@10 of 0.810. Developed and stress-tested an auto-suggestion service sustaining 500 RPS at 180ms p95 latency with zero failures. Designed synthetic data generation pipelines producing 1B+ tokens for retriever and reranker training. ","date":"1 January 2025","externalUrl":null,"permalink":"/experience/","section":"Murad Baghirli","summary":"Graduate Course Assistant # Johns Hopkins University · Baltimore, MD, USA Sep 2026 – Present\nSupport 136 students in Artificial Intelligence (EN.601.464) through weekly office hours and grading. Contribute to course design, including homework and project development. Machine Learning Engineer # National Artificial Intelligence Center (AZBA) · Baku, Azerbaijan Jul 2025 – Aug 2026\n","title":"Experience","type":"page"},{"content":"Selected work across NLP infrastructure, search, and Azerbaijani-language modeling. The current focus is the AzBERT pipeline — a 64k tokenizer plus a NeoBERT-style encoder trained from scratch for Azerbaijani — alongside the production legal-search system at NAIC.\n","date":"1 January 2025","externalUrl":null,"permalink":"/projects/","section":"Projects","summary":"Selected work across NLP infrastructure, search, and Azerbaijani-language modeling. The current focus is the AzBERT pipeline — a 64k tokenizer plus a NeoBERT-style encoder trained from scratch for Azerbaijani — alongside the production legal-search system at NAIC.\n","title":"Projects","type":"projects"},{"content":"","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"}]