In a previous article I showed how to teach a local model a new domain through continued pretraining (CPT). Specifically, I trained the model to act as a travel advisor, capable of navigating a fictional city’s subway system. As a continuation of this experiment, I wanted to explore how a RAG based implementation would compare. The local model used for this is experiment is QWEN 3.5 4B.

RAG vs. CPT

Even though this is a comparison between RAG and CPT for a particular task, it is not my intention to present this as two competing solutions where you pick one or the other. More realistically you would probably see CPT augmented by RAG in many cases to get the best of both worlds. RAG is generally the most flexible solution since no training is required, and your training data won’t get stale, but there are some interesting tradeoffs to consider.

Quick Recap

The original project used CPT to train a local model to act as a travel advisor, providing travel routes for a fictional subway system with up to four line transfers. The previous article showed a successful outcome where the model had generalized the subway map and made it part of its internal knowledge. As an illustration, I have added a snapshot of the subway map below:

Awesomeville subway map showing the Green, Blue, and Gold lines
Awesomeville’s three subway lines connect at Central Station. Select the map to view it full-size.

In this new experiment I swapped out CPT for a simple RAG prompt. A sample of a combined system and user prompt can be found below:

System: Use only the supplied subway-network document for subway facts. Before answering, make one concise internal route check for each requested journey: identify the lines serving the origin and destination, determine whether a transfer is needed, and use Central Station only when changing lines. Verify each journey independently. Do not repeat or reconsider the check after it is complete. Answer in ordinary English. State each line and every required transfer station. Do not invent historic-site facts that are absent from the document. SUBWAY NETWORK: Awesomeville MetroLink subway network All transfers between different lines occur at Central Station. Blue Line, in order: North Terminal, University Commons, Founder's Square, River Market, Central Station, South Gardens. Green Line, in order: Emerald Hills, Museum District Station, Central Station, Innovation Park, Lake Harmony. Gold Line, in order: Westgate, Old Mill Station, Central Station, East Harbor, Sunrise Point. Historic-site subway access stations: Founder's Square is served by Founder's Square; Bright Mill Museum is served by Old Mill Station; Museum of Greatness History is served by Museum District Station; Heritage Theater is served by Museum District Station. User: Plan three consecutive subway journeys: first North Terminal to Emerald Hills, then Emerald Hills to Westgate, then Westgate to South Gardens. Answer in ordinary English. State each subway line, the origin and destination, and each transfer at Central Station. Treat every journey as a fresh route.

Baseline

The CPT training data is the same as in the previous article with the only difference that I upgraded to QWEN 3.5 from 3.0.

I also created a new eval dataset of 100 test cases consisting of route combinations not seen by the model during CPT.

The baseline is using the CPT trained model without thinking enabled.

As before, the results are quite good with an eval score of 93/100 and an average execution time of 1.3s for full subway route recommendations. Enabling thinking brings the score up to 98/100, but at the expense of performance at an avg execution time of 8.7 seconds.

RAG

For the initial RAG implementation, I used the original QWEN 3.5 4B base model with the simple RAG prompt mentioned earlier in the post. Performance was reasonable at an eval score of 80/100. However, performance was much slower at an average execution time of 4.97 seconds with thinking disabled. Enabling thinking would likely improve accuracy, but the performance degradation made it impractical since the runtime degraded to 60 seconds.

The initial prompt used the original subway station names, but I learned during CPT that the model responds better to an internal representation using synthetic names. The synthetic names encode both line membership and ordinals in the name, which makes journey reasoning easier for the small model. See illustration below for the specific mapping. Note: The synthetic names are only used internally and are not user-facing.

Awesomeville Subway Map

Original human-readable station names mapped to the locked synthetic naming convention. Line names remain unchanged; Central Station is the shared interchange represented by central_station.

Blue Line Green Line Gold Line Shared interchange

Blue Line

North Terminal
blue_station_one
University Commons
blue_station_two
Founder's Square
blue_station_three
River Market
blue_station_four
Central Station
central_station
Shared interchange
South Gardens
blue_station_six

Green Line

Emerald Hills
green_station_one
Museum District Station
green_station_two
Central Station
central_station
Shared interchange
Innovation Park
green_station_four
Lake Harmony
green_station_five

Gold Line

Westgate
gold_station_one
Old Mill Station
gold_station_two
Central Station
central_station
Shared interchange
East Harbor
gold_station_four
Sunrise Point
gold_station_five

As expected, using synthetic names helped both accuracy and performance. The new performance values improved to eval score: 88/100 and avg execution time: 3.4s. At this point I would say accuracy between RAG and CPT is close. We could probably make it even closer by improving the RAG prompt.

Performance is where I see the biggest difference though. This is interesting since the retrieval step is intentionally made very simple as a simple file read. I think the interesting part here is quantifying the difference in performance between internalizing the knowledge (CPT) vs. doing on-the-fly reasoning from the RAG content.

Below is a summary table of the performance characteristics across all 4 experiments.

Experiment runtime summary

Configuration Manual pass / fail Min test Max test Avg test Total runtime
CPT, non-thinking 93 / 7 0.644s 3.089s 1.335s 133.550s
CPT, thinking 98 / 2 3.115s 26.927s 8.702s 870.227s
RAG, non-thinking 80 / 20 1.276s 50.959s 4.972s 497.212s
RAG, non-thinking, mapped public → internal names 88 / 12 1.124s 18.997s 3.360s 336.042s

I have included the repo on Github in case you are interested in checking it out.