I Built a 40M-Parameter Multilingual Language Model for Delivery Riders in 28 Minutes. A developer writing on Medium trained a roughly 39.7-million-parameter multilingual language model covering English, Hindi, Bengali, Marathi and Tamil in approximately 28 minutes on a single NVIDIA Tesla T4 GPU using Google Colab's free tier, publishing it on Hugging Face as Indic-Delivery-Rider-SLM. The model is intended to run on-device for delivery riders so common communication tasks work without network connectivity or a cloud inference call, following an earlier approximately 30-million-parameter English-oriented version called Delivery-Rider-Assistant-SLM. The author frames the experiment as a study of how tokenizer design, multilingual data, architecture and training objectives interact under tight compute limits, not as a production-ready assistant. Project: Indic-Delivery-Rider-SLM on Hugging Face https://huggingface.co/learner-subodh/Indic-Delivery-Rider-SLM Languages: English, Hindi, Bengali, Marathi, and Tamil. Training: Approximately 28 minutes on a single NVIDIA Tesla T4 GPU using Google Colab’s free tier. Imagine a delivery rider standing in the basement of a large apartment complex. It is raining. There are two orders on the bike. One customer has just called. “Bhaiya, I entered the wrong flat number. Can you come to B-wing instead?” The rider needs to acknowledge the change, confirm the location, and perhaps ask for a landmark. A simple interaction. Nothing extraordinary. Except the phone has almost no network connectivity. The delivery application is struggling to load. Its cloud-based support assistant cannot be reached. And the rider is left trying to solve a small problem without the digital assistance that the application normally provides. Now imagine something different. A compact language model running directly on the rider’s phone. No API call. No cloud dependency. No waiting for a remote inference server. The model understands the request and suggests a short, polite response. This is the idea behind a Driver SLM — a small, domain-focused language model designed to assist delivery partners and drivers with common operational and communication scenarios. It is a deceptively interesting engineering problem. We have spent the last few years making language models larger, more capable, and more expensive to operate. But not every problem needs a model with hundreds of billions of parameters. Sometimes, the more interesting question is: Can we build a small enough model that is genuinely useful for one specific job, even when the internet is unavailable? I decided to explore that question by building a small language model from scratch, and then pushing the experiment in a more interesting direction: multilingual support. The first version was a compact, approximately 30-million-parameter English-oriented Transformer. You can refer to it here: Delivery-Rider-Assistant-SLM — Hugging Face The next iteration became a roughly 39.7-million-parameter model designed to work across five languages: English, Hindi, Bengali, Marathi, and Tamil. I built the second model using PyTorch on a single NVIDIA Tesla T4 GPU through Google Colab’s free tier. The training run took approximately 28 minutes. That does not mean I built a production-ready multilingual delivery assistant in 28 minutes. The experiment is more useful than that claim would suggest: it gave me a way to study how tokenizer design, multilingual data, architecture choices, training objectives, and evaluation interact when compute and model capacity are limited. This article is about the journey: the problem, the architecture, the algorithms, the engineering decisions, the numbers, and most importantly — the lessons that apply well beyond this particular experiment. I will share the foundations with key implementation details. The objective is to explain how to think about building an SLM, not to publish a copy-and-run reproduction of the project. You can explore the model and its technical details here: Indic-Delivery-Rider-Assistant-SLM — Hugging Face Let’s start with an uncomfortable question. Why would anyone spend time building a 30M-parameter language model when open models with billions of parameters are readily available? Imagine hiring an exceptionally knowledgeable assistant. This person knows almost everything. They can explain physics, write code, summarize research papers, and discuss world history. But they are expensive, take time to reach, and need a reliable internet connection before they can answer your question. Now imagine hiring someone specifically trained for a particular job. They may not know everything, but they understand the situations they encounter every day. That is the intuition behind a domain-specific SLM. For a delivery partner, the useful tasks are relatively focused: A general-purpose LLM is capable of much more than this. But that additional capability comes with resource requirements that may not be justified for every interaction. A small model creates an interesting alternative. 1. Connectivity independence A locally executed model can generate responses without making a request to a cloud inference service. This matters in basements, remote areas, warehouses, highways, and other environments where connectivity is unreliable. 2. Lower inference footprint A model with 30 million parameters has a much smaller weight footprint than a multi-billion-parameter model. At FP16, its raw parameter storage is approximately: 30M parameters × 2 bytes FP16 ≈ 60 MB That is only the weight storage — not the complete runtime memory requirement — but it gives us an idea of the scale. 3. Task specialization Instead of trying to solve every possible language task, we can concentrate model capacity on a narrower problem. This does not automatically make a small model better. Its ability is still constrained by architecture, data quality, and training. But it gives us more control over the system we are trying to build. Not necessarily. If the objective is to achieve the best possible performance with limited development effort, fine-tuning a suitable pretrained model is often the more practical route. A pretrained model already understands a substantial amount of language structure. Training a model from scratch means learning those patterns through your own training process. So why do it? Because building one from scratch is an excellent way to understand what actually happens inside a language model. You work with tokenization, embeddings, attention, positional information, normalization, optimization, backpropagation, and autoregressive generation. You stop treating the Transformer as a black box. For this experiment, that was an important part of the objective. The goal was not to compete with general-purpose LLMs. It was to understand how far a compact, purpose-oriented language model could go under severe compute constraints. The second version of the project is a multilingual, decoder-only language model implemented from scratch in PyTorch. The model is available here: Indic-Delivery-Rider-SLM — Hugging Face https://huggingface.co/learner-subodh/Indic-Delivery-Rider-SLM Model architecture The teal dashed line shows the output projection reusing the embedding matrix. Next is what happens inside each of the 10 blocks. Tokenizer and language coverage Training environment A few architectural decisions are worth examining. First, this is still a decoder-only causal Transformer. It receives a sequence of tokens and learns to predict the next token, making it suitable for autoregressive text generation. The architecture uses 10 Transformer layers, a hidden dimension of 512, and Grouped-Query Attention. It also uses RoPE for positional information, RMSNorm for normalization, and SwiGLU in the feed-forward network. These choices make the implementation closer to the design patterns used in modern decoder-only language models. The tokenizer and training pipeline has been designed to cover five languages. This has effects in almost every stage of the project: vocabulary design, data preparation, sequence lengths, model training, and evaluation. And perhaps the most interesting part: the complete training run took approximately 28 minutes on a single Tesla T4 GPU. That is not a claim that we have built a production-ready mobile assistant in 28 minutes. It is a demonstration that a compact Transformer can be trained and explored with a surprisingly modest compute budget. Being precise about these details matters. Here is one of the most important lessons from building small language models: A small model has very little room to compensate for poor training data. A large pretrained model has already encountered enormous amounts of language. It has learned grammar, vocabulary, common relationships, and many general patterns. A compact model trained from scratch starts with much less prior knowledge. If we give it repetitive, irrelevant, or poorly structured examples, it will learn those patterns too. Imagine teaching a delivery rider using only conversations about delayed orders. The rider may become excellent at apologizing for delays but completely unprepared to handle an incorrect address or an inaccessible apartment. A language model behaves similarly. For an instruction-oriented assistant, a training example can be represented as a user message followed by an assistant response. For example: User: The customer says the delivery address is incorrect. What should I ask? Assistant: Politely ask the customer to confirm the correct building, wing, and flat number before proceeding. Another example: User: The apartment entrance is restricted and security is not allowing entry. Assistant: Contact the customer, explain the access issue, and request instructions for an approved entry point. These examples illustrate the desired interaction format. They are not claims about the exact records used in the experiment. The multilingual experiment uses three sources, with different roles in the training pipeline. 1. Bitext customer-support data I started with the Bitext customer-support instruction dataset as the English conversational foundation. The notebook uses up to 12,000 training examples and 400 validation examples from this source. This provides examples of customer-support interactions and response patterns. It is not a dataset of actual delivery-rider conversations. 2. NLLB-200 translations To extend the experiment to Indian languages, I used the distilled NLLB-200 600M translation model to translate a stratified subset of the English data into Hindi, Bengali, Marathi, and Tamil. The translation pipeline includes filters for issues such as unresolved placeholders, script mismatches, repetition, and unsuitable length. Translation outputs are cached so that the preparation process does not need to be repeated every time the notebook runs. This is an important distinction: translated examples increase language coverage, but they are not equivalent to independently collected and native-speaker-verified conversations. 3. Aya instruction data I also incorporated native-script instruction examples from the Aya dataset for Hindi, Bengali, Marathi, and Tamil. This adds multilingual instruction data beyond machine-translated customer-support examples. However, Aya is general instruction data, not a delivery-specific corpus. The resulting dataset The final prepared dataset contains: The dataset is intentionally mixed. English examples come from Bitext, while the four Indian languages draw from translated Bitext examples and Aya data. The language distribution is not perfectly uniform. For example, English has 12,000 Bitext training examples, while the Indian-language portions combine different quantities of translated and Aya examples. That matters because a multilingual model does not automatically become balanced simply because it has multiple languages in its training data. There are three limitations worth keeping in view. First, the translated examples inherit some of the limitations of machine translation. They need native-speaker review before being treated as reliable training targets. Second, the general instruction examples do not establish delivery-domain expertise. They help expose the model to language and instruction patterns, but they do not teach verified platform policies or operational workflows. Third, the dataset is relatively small for training a multilingual model from scratch. The results should therefore be interpreted as an experiment in architecture and training under constrained compute, not as evidence of broad multilingual competence. For a production-oriented system, I would want a much stronger dataset: real or carefully simulated rider scenarios, native-speaker review, verified operational responses, and explicit coverage of regional expressions and code-switching. The final dataset contained 25,012 examples: 23,816 for training and 1,196 for validation, spanning English, Hindi, Bengali, Marathi and Tamil. English examples came from the Bitext customer-support dataset, while the four Indic languages combined Aya instruction data with machine-translated Bitext examples. This gave the model multilingual training coverage, although the dataset was not a collection of real delivery-rider conversations. Data and licensing note: The experiment combines datasets and model-derived translations with different licensing terms. In particular, the NLLB-200 component is identified as CC-BY-NC-4.0. The resulting model should not be assumed to be cleared for commercial use. Anyone intending to distribute or use the weights commercially should review the applicable dataset and model licenses and obtain appropriate legal guidance. Duplicate leakage Suppose the training set contains: “Customer has entered the wrong flat number.” And the validation set contains: “The customer provided an incorrect apartment number.” These examples are not identical, but they are very similar. If related examples appear in both training and validation, the validation result may look better than actual generalization. Deduplication and careful data splitting are essential. Coverage imbalance If most examples describe delivery delays, the model may become disproportionately good at responding to delays. Inspect the distribution of scenarios rather than relying only on the total number of training records. Incorrect or unsafe responses A model should not learn to invent payment outcomes, make unsupported promises, or recommend actions that contradict platform policy. For sensitive operational situations, verified business rules and human escalation should remain part of the system design. A useful way to think about the dataset is this: The model’s training examples are its operating experience. Repeating the same experience thousands of times does not make it an expert in every situation. Once we have text, we need to convert it into numbers. Neural networks cannot directly process a sentence as a sequence of ordinary words. They process numerical representations. A tokenizer converts text into token IDs. token ids = tokenizer.encode "Please confirm the apartment number." The resulting IDs represent the token sequence according to the tokenizer’s vocabulary. Tokens are not necessarily complete words. They can represent words, subwords, punctuation, and other text units. This is why subword tokenization is useful. A delivery application may encounter building names, abbreviations, unusual spellings, local expressions, and unfamiliar combinations of words. A word-level tokenizer struggles with unseen words. Character-level tokenization can produce very long sequences. Subword tokenization offers a practical compromise. Byte Pair Encoding, or BPE, starts with smaller text units and repeatedly merges frequent adjacent pairs. Suppose a corpus contains many variations of the word “delivery.” The tokenizer may learn recurring combinations of characters or subword units and add them to its vocabulary. Over time, common patterns can be represented by fewer tokens. A simplified pair-merging operation looks like this: python def merge pair tokens, pair, replacement : merged = i = 0 while i < len tokens : if tokens i:i + 2 == list pair : merged.append replacement i += 2 else: merged.append tokens i i += 1 return merged This illustrates the core operation, not a complete BPE training and encoding implementation. The first experiment used the GPT-2 tokenizer through tiktoken. That was convenient, but it was not an ideal choice for a model intended to handle several Indian languages. A tokenizer that represents English efficiently can behave very differently on scripts it was not designed to handle efficiently. Consider the number of tokens required to represent a piece of text. If a tokenizer splits a sentence into too many pieces, the model must process a longer sequence to represent the same content. That affects more than convenience: For this experiment, I trained a SentencePiece BPE tokenizer with a vocabulary of 20,000 tokens. The tokenizer includes byte fallback, digit splitting, and special markers for user messages, assistant responses, and language identification. I also upsampled underrepresented languages in the tokenizer-training corpus, with the aim of giving their scripts sufficient representation in the learned vocabulary. Rather than assuming the new tokenizer was better, I compared it with GPT-2 BPE on held-out text. I measured tokenizer fertility: the number of tokens produced per whitespace-delimited word. Lower values indicate that fewer tokens are needed to represent the same sample under this measurement. The results were: The difference is particularly striking for the Indian-language samples. For Tamil, the measured fertility fell from 23.44 tokens per word to 1.81 92.28% fewer tokens per word . For Bengali, it fell from 12.79 to 1.45 88.66% fewer tokens per word . This is a tokenizer-efficiency result, not a language-understanding result. It does not prove that the model understands Tamil or Bengali better. It shows that the new tokenizer represents the tested text much more compactly. That distinction matters. Better tokenization can make multilingual training more practical, but it cannot compensate for insufficient data, weak training, or poor evaluation. For a small model, tokenizer design is not a preprocessing detail. It is part of the model architecture and its parameter budget. Here is where tokenization becomes an architectural decision. Every vocabulary token requires an embedding vector. The embedding matrix contains 50,000 tokens, each represented by a 384-dimensional vector. 50,000 × 384 = 19.2 million parameters. That is 19.2 million parameters in the embedding table alone. In a model with only 30 million parameters, that would be an enormous allocation. This is why vocabulary design is especially important for compact models. A smaller domain-oriented vocabulary could reduce the embedding footprint. But making it too small can increase sequence length because common words are split into more pieces. There is a trade-off between: In this experiment, I used GPT-2 BPE encoding through tiktoken. It is a convenient, established tokenizer choice, rather than a custom tokenizer trained specifically for delivery language. For a future version, I would investigate domain-oriented vocabulary design as an explicit optimization step. Now we have token IDs. How does the model learn to understand their relationships? We use a decoder-only Transformer. The idea is easier to understand if we break it into components. A token ID is just an integer. An embedding layer maps it into a learned vector. If the vocabulary size is V and the embedding dimension is d , the embedding matrix is: E ∈ R^ V × d The model looks up the vector corresponding to each input token. These vectors are learned during training. We do not manually assign semantic meanings to them. Consider: “The rider called the customer.” vs. “The customer called the rider.” The same words appear, but the meaning is different. A Transformer therefore needs a way to represent token positions. This version uses Rotary Position Embeddings, commonly called RoPE. Instead of simply adding a learned position vector to each token embedding, RoPE applies position-dependent rotations to query and key representations inside the attention mechanism. The important intuition is that the attention calculation becomes sensitive to the relative positions of tokens. This is useful because language is not just a collection of words. The relationship between two words also depends on where they appear in a sequence. RoPE has become a widely used positional-encoding approach in modern decoder-only Transformer architectures. This is the heart of the Transformer. Imagine the model reading: “The customer requested a different address because the original building was closed.” When processing the end of the sentence, it needs to connect the current information with what appeared earlier. Attention lets each token determine which other tokens in the available context are relevant. It uses three representations: The scaled dot-product attention equation is: Attention Q, K, V = softmax Q × Kᵀ / √dₖ × V The intuition: In other words, attention lets the model look back at relevant parts of the input rather than treating every token independently. A language model predicts the next token. During training, it must not see future tokens when making that prediction. This is enforced using a causal mask. A simplified implementation using PyTorch’s scaled-dot-product attention looks like this: y = F.scaled dot product attention q, k, v, is causal=True The causal setting prevents a position from attending to future positions. Without it, the model could indirectly access information it is supposed to predict. Why have multiple attention heads? Different heads can learn different relationships. One may focus on nearby tokens. Another may learn relationships between words that are farther apart. Another may respond to structural or grammatical patterns. The outputs of these heads are combined to form a richer representation. This model uses Grouped-Query Attention GQA , with eight query heads and four key/value heads. In standard multi-head attention, each query head has its own key and value head. GQA allows multiple query heads to share a key/value head. The benefit is a more compact key/value representation, which can reduce the memory required for the attention cache during autoregressive inference. For this architecture, eight query heads share four key/value heads. It is a practical compromise between the expressiveness of multi-head attention and the efficiency benefits of multi-query attention. Attention gathers information from the sequence. The feed-forward network transforms the representation at each position. This model uses SwiGLU, a gated feed-forward design. At a high level, SwiGLU uses two learned projections: one contributes the content representation, while the other provides a learned gate. Their interaction is followed by a projection back to the model dimension. The gating mechanism gives the network a more flexible way to control which information flows through the feed-forward block. The feed-forward hidden dimension in this experiment is 1,408. This is one of the architectural differences between the original compact GPT-style experiment and the newer Llama-style implementation. Deep networks need stable information and gradient flow. Residual connections provide a direct path around a transformation: y = x + f x This implementation uses RMSNorm rather than standard LayerNorm. RMSNorm normalizes representations using their root-mean-square magnitude. It does not perform the mean-centering step used by LayerNorm. It is a simpler normalization operation that is widely used in modern decoder-only Transformer designs. Together, residual connections and RMSNorm help maintain stable information and gradient flow through the Transformer blocks. Together, these components make it easier to train multiple Transformer blocks. The architecture follows this high-level flow: Input Token IDs | vToken Embeddings | v+-----------------------------+| 10 Transformer Blocks || || RoPE + GQA + RMSNorm || SwiGLU Feed-Forward |+-----------------------------+ | vFinal RMSNorm | vVocabulary Projection | vNext-Token Logits The model has 10 Transformer layers, a hidden dimension of 512, eight query heads, and four key/value heads. The input and output embeddings are tied, meaning the model shares the embedding weights with the output vocabulary projection rather than learning two independent matrices. The result is a compact decoder-only architecture that incorporates several design choices used in modern language models, while remaining small enough to train in a constrained environment. One of the most interesting parts of building a small model is understanding where the parameters go. A model’s parameter count is not simply a measure of its size. It is an allocation decision. We must distribute capacity across: Increasing the hidden dimension can improve representational capacity, but it also increases the size of several matrices. Increasing the number of layers adds depth, but also increases computation and memory requirements. Increasing the vocabulary can make tokenization more efficient, but may substantially increase embedding parameters. The model contains approximately 39.7 million parameters. The parameter breakdown is particularly interesting: The embedding table is still a substantial part of the model, but its share is considerably more controlled than in the original experiment. This is one reason the tokenizer change mattered. A vocabulary of 20,000 tokens, combined with a hidden dimension of 512, gives an embedding allocation of approximately 10.2 million parameters. The remaining capacity is available for the Transformer layers, attention projections, feed-forward networks, and normalization components. I also retained weight tying between the input embeddings and output projection. This avoids maintaining two separate sets of vocabulary-sized weights. The broader lesson is that parameter count should be treated as a budget. Vocabulary size, hidden dimension, layer count, attention design, and feed-forward width all compete for that budget. A model with more parameters is not automatically better. The question is whether those parameters are allocated in a way that supports the task and the constraints of the target system. There is no free improvement. One useful technique is weight tying. A language model has an input embedding matrix and an output projection that maps hidden states back to vocabulary logits. These can share weights: W output = E tokenᵀ This reduces the number of independent parameters. For compact models, that saving can matter. I initially made a simple mistake: I estimated the model size mentally. The project description said 45M parameters. The actual model was approximately 30M. That is a good reminder to measure rather than guess. total params = sum p.numel for p in model.parameters print f"{total params / 1e6:.2f}M parameters" It is a small detail, but it reflects an important engineering habit: verify the actual artifact, not the configuration you intended to build. We now have the data, tokenizer, and Transformer. The next step is training. The objective is next-token prediction. “The rider reached the building” The model receives the sequence and learns to predict the next token at every position. The input and target sequences are shifted versions of each other. Step 1 Input: The Target: rider Step 2 Input: The rider Target: reached Step 3 Input: The rider reached Target: the Step 4 Input: The rider reached the Target: building At each training step, the model receives the preceding tokens as context and learns to predict the next token. As the sequence grows, the context expands, allowing the model to learn how words relate to one another. The model produces logits representing its predictions over the vocabulary. We calculate cross-entropy loss against the correct next tokens. The objective can be written as: Loss = - 1 / N × Σ log P xᵢ | previous tokens The model is penalized when it assigns a low probability to the actual next token. Backpropagation calculates how each parameter contributed to the error, and the optimizer updates the parameters. This process repeats across batches. The model is trained using next-token prediction, but the training objective includes an important detail: different parts of the formatted sequence receive different loss weights. The examples contain user messages, assistant responses, language markers, and structural tokens. I used response-weighted loss: Why do this? The objective is to build an instruction-following model, not simply to reproduce the entire input sequence equally well. The user’s prompt provides context. The assistant’s response is the primary output we want the model to learn to generate. Giving response tokens a higher weight places more emphasis on the part of the sequence that matters most for the intended interaction. The prompt is not ignored; it still contributes to the learning process, but with a smaller weight. The optimizer determines how the model’s parameters change during training. Adam maintains moving averages of gradients and squared gradients, allowing adaptive parameter updates. AdamW separates weight decay from the gradient-based update. This is an important distinction from simply adding an L2 penalty to the loss. The experiment used AdamW with: The learning rate followed a warmup-plus-cosine schedule. Warmup gradually increases the learning rate during the early part of training. Cosine decay then reduces it towards the minimum learning rate. The schedule was tied to the available training-time budget, which is useful when working in a constrained notebook environment. The training run used FP16 automatic mixed precision. The idea is to use lower-precision operations where appropriate to reduce memory consumption and improve hardware efficiency. This is particularly useful when working with limited GPU memory. However, precision support depends on the actual GPU and runtime. The important engineering habit is to verify the precision mode and actual performance rather than assume that a configuration is efficient simply because it executes. The broader lesson: A training script that runs is not necessarily a training script that runs efficiently. Always check the hardware, precision mode, and actual performance. The learning rate controls the size of parameter updates. Starting with an aggressive learning rate can destabilize early training. Warmup gradually increases the learning rate before the main training phase. Cosine decay then reduces it smoothly. Think of it as accelerating gradually and slowing down as you approach the destination. This is a common and useful training strategy for Transformer models. The second model was trained on a single NVIDIA Tesla T4 GPU through Google Colab’s free tier. The actual run lasted approximately 28.1 minutes. A few numbers help put the run into perspective: The model was trained using FP16 mixed precision and AdamW, with a warmup-plus-cosine learning-rate schedule. The best response-only validation negative log-likelihood was approximately 1.925, reached around 17.8 minutes into training. Training loss continued to decrease after that point, eventually reaching approximately 0.622 near the end of the run. However, validation loss began increasing after its best point and reached approximately 1.982. That divergence is an early overfitting signal. It is also a useful reminder that the final checkpoint is not necessarily the best checkpoint. The experiment retained the checkpoint with the best validation result rather than simply using the last set of weights. One important clarification: this validation metric is not directly comparable with the 0.7974 validation loss from the earlier model. The two experiments use different model architectures, tokenizers, datasets, and loss formulations. The newer experiment also reports response-only validation NLL. Comparing the two numbers as if they measured the same task under the same conditions would be misleading. First: the training budget is an engineering constraint. A free GPU session is not an unlimited resource. Data preparation, training, evaluation, checkpointing, and export all compete for time. The pipeline needs to be designed around the actual available compute. Second: the best checkpoint may occur before training ends. The training loss continued improving while validation loss began deteriorating. Tracking validation performance and retaining the best checkpoint is essential. Third: a lower loss is not the same as a better assistant. The loss curve tells us how well the model predicts held-out tokens under the chosen objective. It does not tell us whether a rider would receive a correct, concise, or operationally safe answer. That requires a separate evaluation process. First: evaluation consumes compute. It is tempting to evaluate hundreds of batches after every few training iterations. But evaluation requires forward passes too. On a free GPU, excessive evaluation can consume a significant portion of the available training budget. Use representative evaluation batches and choose sensible evaluation intervals. Second: gradient accumulation changes the training schedule. When using gradient accumulation, the learning-rate scheduler should advance with optimizer updates, not with every micro-batch. Otherwise, the schedule can finish earlier than intended. Third: checkpoint selection matters. The last training checkpoint is not necessarily the best one. Track validation loss and preserve the best checkpoint. A compact model trained on a limited dataset can overfit sooner than expected. This is where the real work begins. A language model can generate fluent sentences and still misunderstand the problem. Consider this prompt: “The customer says the building entrance is closed, but the order is marked as delivered.” A response might sound professional but recommend an incorrect action. That is not a successful result. For a practical assistant, we need to evaluate behavior rather than rely exclusively on training metrics. I evaluated the model at two different levels: held-out language-model performance and a small set of generation checks. The first gives us a quantitative view of next-token prediction. The second gives us a preliminary look at what the model actually generates. Token-level loss is difficult to compare across languages when different tokenizers split text into very different numbers of tokens. To get a more comparable view of the model’s held-out performance, I also examined bits per byte. The measured domain-data results were: For the general-data evaluation: A general-data result was not reported for English in this evaluation. Lower bits per byte indicates better predictive performance on the evaluated text under this metric. It does not establish task correctness or conversational quality. These values should also be interpreted within the limits of the evaluation data. They are not a substitute for a representative benchmark of real delivery-rider interactions. I also tested generation using three hand-written prompts per language. The checks included: The automatic language-identification check returned 1.00 for all five languages on this small prompt set. However, three prompts per language are far too few to establish robust language identification or reliable multilingual performance. Script-based checks can also mistake text in the right script for a correct answer. The generation results were mixed. Some outputs were recognizably in the requested language, but others were generic, incomplete, or irrelevant to the prompt. For example, one Marathi generation was only: “जयसिंगाने” That is not a useful answer to a delivery-support request. It is a clear failure, and it is important to show it rather than hide it behind aggregate metrics. The English outputs were more fluent, but some included unsupported promises such as saying that an address would be updated immediately, even though the model was not connected to an order-management system. This is exactly why generation quality must be evaluated separately from script detection and language-model loss. The experiment demonstrates that a small model can be trained with a multilingual tokenizer and data pipeline covering five languages. It also provides preliminary evidence that the tokenizer represents the tested Indian-language text more efficiently than GPT-2 BPE. It does not establish that the model is a reliable delivery assistant in all five languages. The generation checks are small, the data is not a field-collected delivery corpus, and the outputs still contain clear failures. A more serious evaluation would require: The model should not independently decide payment, safety, or dispute outcomes. Verified business systems must remain the source of truth. The model can help formulate communication, but it should not invent operational decisions. For me, this is one of the most valuable findings from the experiment: a multilingual model that can produce text in a script is not necessarily a multilingual assistant that can solve the user’s problem. These are short excerpts from the model’s saved generations. They are included to show both the progress and the limitations of the experiment. English “Please provide us with the new address, and we will make sure to update it for you right away.” Hindi “हम आपकी चिंताओं को शीघ्रता से संबोधित करने के लिए प्रतिबद्ध हैं।” Bengali “আশ্বস্ত হোন, আমি এখানে আছি আপনাকে প্রক্রিয়াটি পরিচালনা করতে এসেছি।” Marathi Tamil “இந்த செயல்முறையில் உங்களுக்கு உதவ எங்கள் குழு இங்கே உள்ளது.” These are raw excerpts, not human-verified translations or evidence of task success. The Marathi example is visibly incomplete, and the English excerpt makes an operational promise the model cannot independently fulfill. Native speakers should review the examples before they are treated as meaningful language-quality demonstrations. I find four categories particularly useful. Fluent and correct: The response is relevant, understandable, and appropriate. Fluent but incorrect: The response sounds convincing but gets the situation wrong. Off-topic: The model fails to address the actual request. Repetitive: The model repeats phrases or gets stuck in a generation loop. The second category is especially important. A confident wrong answer can be more problematic than a model that simply asks for clarification. For a real deployment, I would go further: The evaluation dataset should be kept separate from training data, ideally with scenario-level separation to reduce leakage. And one rule should be non-negotiable: The model should not independently decide sensitive payment, safety, or dispute outcomes. Verified business systems should remain the source of truth. The model can help communicate information, but it should not invent operational decisions. Training a model is not the same as deploying an application. For this version, I added an export path that maps the custom model’s weights into a Hugging Face Llama-compatible model structure. The notebook constructs a LlamaForCausalLM configuration, transfers the model weights, and includes a numerical comparison between the custom implementation and the exported model's logits. The purpose of this check is to verify that the exported model preserves the original model’s forward-pass behavior. The notebook contains the comparison code, but the output was not saved in the version I reviewed. I would therefore rerun that cell and capture its numerical result before claiming that the equivalence check passed. The export also includes the SentencePiece tokenizer model and a format configuration file. The SentencePiece model remains important: the tokenizer should not be assumed to be interchangeable with an arbitrary tokenizer simply because the model weights have been exported into a standard architecture. The artifact is not automatically interchangeable with an arbitrary Hugging Face AutoModel architecture. A deployment system needs to know how the model was constructed. Thus, it would need to: The model export is a useful step towards interoperability. It is not evidence that the model has already been deployed on a phone. A simplified generation process looks like: User message | vTokenizer | vToken IDs | vTransformer forward pass | vNext-token probabilities | vToken selection | vAppend token and repeat | vDecode response The model predicts one token at a time. The application controls when generation stops, how many tokens may be generated, and how the response is presented. For a short delivery interaction, concise generation is often preferable to an elaborate answer. A model with approximately 39.7 million parameters has a relatively small raw weight footprint. Using the parameter count and nominal precision, the approximate storage requirements for the weights alone are: These are approximate weight-storage estimates, not measurements of a complete deployed application. Actual memory requirements also include the model runtime, tokenizer assets, intermediate tensors, key/value cache, and application components. The notebook also includes a CPU forward-pass sanity check. On the tested environment, it measured approximately 14.4 forward passes per second using two CPU threads, without a KV cache. That is useful as a basic execution check, but it is not a mobile-device benchmark. It should not be interpreted as tokens per second or as evidence of acceptable phone latency. I have not established a production mobile deployment, nor completed GGUF or ONNX conversion and device-level benchmarking. Those remain future engineering steps. For a genuine edge deployment, I would measure: A model that runs on a desktop CPU or training GPU may behave very differently on a mobile device. The target hardware must be part of the evaluation process, not an assumption made from parameter count alone. These are theoretical weight-storage estimates, not complete application memory requirements. Actual deployment also needs memory for the runtime, tokenizer, KV cache, intermediate tensors, and application components. Quantization can reduce the model footprint, but it may affect output quality. The right way to evaluate it is to compare the original and quantized models on the same representative scenarios. For a mobile deployment, I would measure: A model that performs well on a training GPU may behave very differently on a low-cost mobile device. That is why deployment benchmarking must happen on the actual target hardware. There is an important distinction I want to make about this project. This was a fun, compact engineering exercise — not a claim that a production-grade Driver SLM can be delivered in 23 minutes. The objective was to understand the foundations of building and training a language model from scratch, using a constrained compute environment. But the underlying training fundamentals are not limited to a 30M-parameter experiment. The same broad workflow can be extended to a much more serious system: The SLM Development Pipeline What changes is the scale, rigor, and supporting infrastructure. 1. Domain-specific data engineering The general customer-support dataset would be supplemented or replaced with carefully curated delivery-specific examples. This would involve realistic rider interactions, approved operational workflows, language variations, and verified responses. Data governance, privacy, consent, licensing, and quality controls would become first-class requirements. 2. Stronger evaluation We would need a proper benchmark covering scenario coverage, correctness, robustness, safety, and generalization. We would also compare the from-scratch model against a fine-tuned pretrained SLM. The goal would be to justify the architecture through evidence, not preference. 3. System-level architecture A production assistant would not rely on a language model for every decision. It could combine: This is an important architectural principle. A useful AI product is often a system of cooperating components, not a model operating in isolation. 4. Mobile inference optimization The model would be exported to a suitable mobile runtime, quantized, and benchmarked on representative low-end devices. Memory, latency, power consumption, and offline behavior would become explicit engineering objectives. 5. Continuous evaluation and improvement A production system would need monitoring, failure analysis, versioning, regression tests, and a controlled update process. The model should improve based on validated evidence rather than blindly learning from every interaction. The important takeaway is that the foundational training recipe remains relevant. The data pipeline, tokenizer, architecture, optimization, validation, and generation process are the same broad building blocks. Production engineering adds scale, governance, reliability, integration, and much stronger evaluation. That is why I find this experiment valuable beyond the immediate use case. Every useful experiment should leave us with a better set of questions. This version answered some questions about multilingual tokenization and constrained training, but it also exposed several areas that need more work. If I were taking the project forward, I would prioritize five things. 1. Improve the quality of multilingual data. The current pipeline combines English customer-support data, machine-translated examples, and general multilingual instruction data. The next step would be native-speaker review and a more carefully curated set of delivery-specific interactions in each language. 2. Build a proper multilingual evaluation benchmark. Three prompts per language are useful for an initial smoke test, not for establishing reliable language performance. I would build a larger held-out benchmark with scenario-level separation, native-speaker review, task correctness criteria, and explicit tests for unsupported claims. 3. Compare against pretrained multilingual baselines. Training from scratch was valuable for understanding the architecture and training process. But it does not establish that training from scratch is the best way to build this application. I would compare the model with suitable pretrained multilingual small language models fine-tuned on the same data. 4. Improve response quality and operational grounding. The model should learn to ask for clarification when information is missing and avoid promising actions it cannot perform. For real order, payment, and delivery information, the assistant should rely on verified application APIs and deterministic business logic rather than generating answers from model parameters alone. 5. Validate actual edge deployment. The model’s parameter count makes mobile inference worth investigating, but the CPU sanity check is not a substitute for phone testing. I would export and quantize the model, benchmark it on representative devices, and measure latency, memory, battery consumption, and response quality. The next iteration should be driven by those measurements rather than by model size alone. The Driver SLM is one example of a much broader opportunity. Think about people who work outside conventional office environments. Agricultural workers in areas with poor connectivity. Field technicians who need quick access to repair procedures. Delivery partners who handle repetitive customer interactions. Industrial workers who need context-specific instructions. Remote teams that cannot depend on a reliable cloud connection. Not every AI interaction requires a giant model. Sometimes, the task is narrow, the vocabulary is predictable, and the environment imposes strict constraints on connectivity, latency, cost, and hardware. That creates an interesting opportunity for small language models. The challenge is not simply to shrink an existing model. It is to understand the task, choose the right architecture, prepare the right data, and define what useful performance actually means. For me, the most valuable part of this experiment was not simply getting a Transformer to train. It was seeing how decisions that can look like isolated implementation details — tokenizer vocabulary, language sampling, attention design, loss weighting, validation strategy, and checkpoint selection — interact when model capacity and compute are limited. The first version helped me understand the mechanics of building a compact language model. The multilingual version made the engineering problem more interesting: the model had to account for different scripts, different tokenization behavior, different data sources, and uneven evaluation quality. It also reinforced an important lesson: a successful training run is only one milestone. The real work is establishing whether the model solves the intended problem reliably. The future of AI will not be defined only by who builds the largest model. It will also be shaped by who can identify the right problem, build an appropriately sized solution, and validate it where it is actually needed. Sometimes, a 40-million-parameter model and a clearly defined engineering question are a very good place to start. It will not reliably solve every unfamiliar problem. It should not make consequential decisions without verification. And this particular experiment still needs much stronger data, evaluation, and device-level testing before it can be trusted in a real delivery workflow. But it is a useful starting point for exploring a different approach to AI deployment: Small. Specialized. Multilingual. And designed around the constraints of the person who needs it. One of the most interesting things about this project wasn’t just that I managed to train a language model from scratch. It was how little infrastructure I needed to get there. There’s a common perception that building language models requires expensive GPUs, large compute clusters, substantial cloud budgets, and access to enterprise-grade infrastructure. That perception is understandable when we’re talking about frontier-scale models. But it doesn’t have to be true for every AI problem. For the multilingual version, I used: The training run took approximately 28.1 minutes. There was also a separate data-preparation stage involving translation and tokenizer preparation. The notebook estimates approximately 15–20 minutes for this one-time preparation, with generated translations and tokenizer artifacts cached for reuse. To put the training run in context, consider the AWS EC2 g4dn.xlarge instance. It provides one NVIDIA T4 GPU, four vCPUs, 16 GiB of system memory, and local NVMe storage. Using indicative Linux On-Demand rates: The 28.1-minute training run alone would therefore cost approximately: If the same GPU instance remained allocated for the 15–20 minutes of preparation and the 28.1-minute training run, the combined 43–48.1 minutes would cost approximately: These are indicative compute-only estimates, not an AWS experiment I actually ran. They exclude storage, data transfer, taxes, provisioning overhead, and other possible charges. Actual pricing should be checked using the AWS Pricing Calculator. The point is not that an enterprise-ready multilingual assistant costs less than a dollar to build. It does not. Data curation, native-speaker review, engineering, evaluation, security, integration, deployment, and maintenance are much larger parts of a serious product effort. The narrower point is that a small Transformer experiment can be conducted with a surprisingly modest amount of GPU time. At approximately 39.7 million parameters, the model’s nominal weight-storage requirements are: These figures cover weights alone. They do not include the memory required by the runtime, tokenizer, intermediate tensors, key/value cache, or the surrounding application. Quantization could make the model easier to distribute and potentially run in a resource-constrained environment. But the actual result would need to be measured on the target device, including any loss in response quality. The important distinction is between a model that is small enough to make edge deployment worth investigating and one that has actually been proven to run well on a phone. This experiment establishes the former, not the latter. No dedicated GPU server. No paid cloud infrastructure. No distributed training setup. No expensive proprietary training framework. The actual training run took approximately 28 minutes. Google Colab was convenient for this experiment, but let’s look at the economics from an enterprise engineering perspective. Companies typically use cloud infrastructure rather than relying on free notebook environments. So, what would it cost to reproduce this training run using a commercial cloud provider? AWS provides an interesting comparison through its EC2 g4dn.xlarge instance, which comes with: This is a close GPU-backed cloud equivalent to the hardware used in our experiment. Using indicative AWS Linux On-Demand pricing: Our actual training run took approximately 23 minutes. That translates to: These are compute-only estimates. A real engineering workflow would also account for instance provisioning, environment setup, storage, data transfer, experimentation, and evaluation. Even if we allocate a full hour of GPU-instance time, the compute expenditure would be approximately $0.53–$0.58. That’s a useful perspective. We trained a 30-million-parameter language model from scratch, using a GPU-backed environment that is also available through a standard commercial cloud provider, without requiring an expensive multi-GPU cluster. Of course, this is not the cost of developing an enterprise-ready AI product. Engineering time, domain-specific data, comprehensive evaluation, security, deployment, monitoring, and ongoing inference all contribute to the total cost of ownership. But it demonstrates that the cost of experimenting with and training a reasonably sized language model can be surprisingly small. The important engineering decision is to match the model architecture, dataset, and training objective to the problem rather than automatically reaching for the largest available infrastructure. Pricing reference: AWS EC2 G4dn instance specifications and indicative regional On-Demand pricing. Prices are subject to change and should be verified using the AWS Pricing Calculator . Google explicitly provides free access to computing resources, including GPUs, through Colab. However, free-tier GPU availability and usage limits are dynamic, and a particular GPU is not guaranteed for every session. This makes it an excellent environment for educational projects and appropriately sized experiments, rather than a dependable foundation for production training infrastructure. The answer comes down to matching the model architecture and training workload to the available hardware. We weren’t attempting to train a multi-billion-parameter foundation model. We were building a compact, approximately 39.7-million-parameter multilingual Transformer with: These architectural choices kept the model’s computational requirements manageable while allowing us to experiment with five languages: English, Hindi, Bengali, Marathi, and Tamil. We also used FP16 automatic mixed-precision training. Instead of performing every operation in full 32-bit floating-point precision, compatible operations could use 16-bit precision. This helped reduce memory requirements and made better use of the available GPU. The training workload was also deliberately constrained. The prepared dataset contained 23,816 training examples and approximately 3.045 million tokens per epoch. We used a training-time budget of approximately 28 minutes, completing 2,239 optimizer steps over roughly 18.1 epochs. The other important decision was keeping the model and dataset appropriately scoped. We weren’t trying to teach the model everything about language, the world, or every possible domain. We were experimenting with a compact multilingual model using a manageable combination of customer-support data, machine-translated examples, and multilingual instruction data. In other words, we didn’t solve a large infrastructure problem by buying more infrastructure. We designed the experiment around the infrastructure we had. A single T4 was sufficient to conduct this training experiment. That doesn’t mean it would be sufficient for every stage of a production multilingual assistant, particularly large-scale data preparation, extensive evaluation, or production inference. Compute cost is only one part of the economics of building an SLM. Model size also affects storage and deployment. For a model with approximately 30 million parameters, the theoretical weight-storage requirements are: Weight precisionApproximate weight storage: FP32: 120 MB FP16: 60 MB INT8: 30 MB INT4: 15 MB These are estimates for model weights alone. They do not include tokenizer assets, runtime overhead, activations, KV cache, or other memory requirements. Nevertheless, the difference is significant. A compact model can be much easier to distribute and potentially run on resource-constrained devices than a substantially larger model. This is particularly relevant to the original motivation behind the Delivery Rider Assistant SLM: exploring the possibility of local, offline assistance on mobile devices. Quantization, runtime optimization, and device-specific benchmarking would be necessary steps before making any production deployment claims. But the economics of a small model give us a useful starting point. There is an important distinction I want to make here. Training a model cheaply is not the same as building a good model. The multilingual experiment produced a compact model, a tokenizer with substantially improved measured fertility on the tested Indian-language samples, and preliminary held-out evaluation results. But the generation tests also exposed obvious weaknesses. Some outputs were generic, some did not answer the prompt, and at least one Marathi output was incomplete. The training data was a mixture of general customer-support examples, machine-translated examples, and general multilingual instruction data. It was not a verified corpus of actual delivery-partner conversations. The model has also not been validated on real phones or integrated with order-management, payment, or delivery systems. So the delivery-rider assistant remains the intended application, not a proven production capability. A production-oriented version would require native-speaker-reviewed data, verified delivery-specific scenarios, systematic task evaluation, operational integrations, mobile-device benchmarking, and mechanisms for handling uncertain or unsupported requests. The point is not that a free GPU can replace an entire production AI infrastructure. It cannot. The point is that we can use modest infrastructure to investigate serious engineering questions — and be precise about what the resulting evidence does and does not show. I find this particularly interesting from an engineering perspective. It’s tempting to equate more parameters, larger GPUs, bigger datasets, and higher infrastructure budgets with better AI. But those resources only create value when they are justified by the problem being solved. For a narrowly defined application, a smaller model with an appropriate architecture, carefully selected training data, and a well-designed evaluation strategy may be a much more practical starting point. It can also open up possibilities that are difficult to explore with larger models: local inference, reduced network dependency, lower deployment costs, and greater control over the application. This is why I consider building this SLM a useful exercise beyond the model itself. It demonstrates the fundamentals of model architecture, tokenization, optimization, training, evaluation, and deployment without requiring an expensive infrastructure setup. And perhaps that’s the most encouraging takeaway from the project: Building good AI systems isn’t always about spending more. Sometimes, it’s about understanding the problem well enough to know how little you actually need to get started. Let’s return to the basement parking lot. The rider is still waiting. The network is still unreliable. The customer still needs a response. A cloud model may be more capable, but it cannot help if the application cannot reach it. A small local model offers another possibility. It may not understand every situation. It may need verified business logic, careful guardrails, and human escalation. It may need several rounds of data and architecture improvements before it becomes genuinely useful. But it can run close to the user, respond without a network connection, and focus on a narrow set of problems. It was understanding how tokenization, parameter allocation, attention, optimization, numerical precision, and evaluation interact when every megabyte and every training step matters. And perhaps that is the most interesting lesson. It will also be shaped by who can identify the right problem, build the smallest effective solution, and deliver it reliably where it is needed. Sometimes, 30 million parameters and a clear purpose are a pretty good place to start. Architecture: Approximately 39.7M-parameter Llama-style decoder-only Transformer Training: Single NVIDIA Tesla T4 Environment: Google Colab Free Tier Training time: Approximately 28.1 minutes. This project is an educational and experimental prototype. Its results should not be interpreted as evidence of production readiness. A production deployment would require delivery-specific data, systematic task and safety evaluation, device-level benchmarking, verified operational integrations, and appropriate human oversight. I Built a 40M-Parameter Multilingual Language Model for Delivery Riders in 28 Minutes. https://pub.towardsai.net/i-built-a-40m-parameter-multilingual-language-model-for-delivery-riders-in-28-minutes-db1b51e42475 was originally published in Towards AI https://pub.towardsai.net on Medium, where people are continuing the conversation by highlighting and responding to this story.