<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chidozie Uzoegwu</title>
    <description>The latest articles on DEV Community by Chidozie Uzoegwu (@chidozie).</description>
    <link>https://dev.to/chidozie</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2216407%2F113e9948-f299-4a9a-8b17-807a118b37c4.jpeg</url>
      <title>DEV Community: Chidozie Uzoegwu</title>
      <link>https://dev.to/chidozie</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chidozie"/>
    <language>en</language>
    <item>
      <title>Question 55, 30 Minutes Left: How I Prepared for the AWS GenAI Developer Professional Exam</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Tue, 13 Oct 2026 09:35:05 +0000</pubDate>
      <link>https://dev.to/aws-builders/question-55-30-minutes-left-how-i-prepared-for-the-aws-genai-developer-professional-exam-5498</link>
      <guid>https://dev.to/aws-builders/question-55-30-minutes-left-how-i-prepared-for-the-aws-genai-developer-professional-exam-5498</guid>
      <description>&lt;p&gt;I passed the AWS Certified Generative AI Developer - Professional exam (AIP-C01) recently. At one point I looked at the clock and I was on question 55 with about 30 minutes left.&lt;/p&gt;

&lt;p&gt;I was reading every question deliberately, making sure every condition in it was covered before I committed to an answer. This exam punishes skimming. Most of the wrong options are technically real AWS features that answer part of the question, and the part they miss is usually one clause you read too quickly.&lt;/p&gt;

&lt;p&gt;This is the guide I wanted when I started: what the exam covers, what I studied, how I learned to read the questions, what each of the main services is actually for, and the traps that come up again and again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exam at a glance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;75 questions, of which 65 are scored and 10 are unscored. You can't tell which are which.&lt;/li&gt;
&lt;li&gt;180 minutes, which works out at 2.4 minutes per question.&lt;/li&gt;
&lt;li&gt;Scaled score from 100 to 1,000. You need 750 to pass.&lt;/li&gt;
&lt;li&gt;Multiple choice has one right answer out of four. Multiple response has two or more right answers out of five or more, and you must pick all of them to get any credit.&lt;/li&gt;
&lt;li&gt;No penalty for guessing, and unanswered questions are marked wrong. Never leave a blank.&lt;/li&gt;
&lt;li&gt;Scoring is compensatory, so you don't need to pass each domain. You need to pass the exam overall.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Domain&lt;/th&gt;
&lt;th&gt;Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1. Foundation Model Integration, Data Management, and Compliance&lt;/td&gt;
&lt;td&gt;31%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2. Implementation and Integration&lt;/td&gt;
&lt;td&gt;26%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3. AI Safety, Security, and Governance&lt;/td&gt;
&lt;td&gt;20%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4. Operational Efficiency and Optimization for GenAI Applications&lt;/td&gt;
&lt;td&gt;12%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5. Testing, Validation, and Troubleshooting&lt;/td&gt;
&lt;td&gt;11%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Domains 1 and 2 are 57% of the scored content between them. If your time is limited, that is where it goes.&lt;/p&gt;

&lt;p&gt;The exam guide also lists what is out of scope: model development and training, advanced ML techniques, and data and feature engineering. This is an exam about building with foundation models on AWS, so don't spend a week on training theory.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I used
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The main course.&lt;/strong&gt; Stéphane Maarek and Frank Kane's "Ultimate AWS Certified Generative AI Developer - Professional" course on Udemy, with its full-length practice exam. It follows the official exam guide closely and has hands-on labs, which matter for this exam because so many questions turn on what a specific API or parameter actually does.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QA learning platform.&lt;/strong&gt; I had access through the AWS Community Builders programme and used it alongside the course.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Skill Builder.&lt;/strong&gt; AWS's own training platform, with official exam preparation material for this certification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The official exam guide.&lt;/strong&gt; Each domain lists tasks and skills, and many skills include a "for example" list of AWS services. Those lists are a good map of where answer options come from. I turned the whole guide into a checklist and rated myself on every skill: built it, know it but would hesitate between it and a neighbouring service, or couldn't pick it out of four options. The third group became my study plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The in-scope services list.&lt;/strong&gt; It sits in the exam guide too. When I compared it with my practice material, a handful of in-scope services never appeared in any practice question at all, including Cost Explorer and Cost Anomaly Detection. Practice questions can only test what their authors wrote, so read the in-scope list yourself and fill the gaps from the docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS documentation, checked live.&lt;/strong&gt; Whenever a practice explanation and my own understanding disagreed, I went to the current AWS docs and kept a log of what I checked. Bedrock changes fast. A few practice explanations had fallen behind the service, and the docs were right every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hands-on work.&lt;/strong&gt; I build and run a production generative AI system on AWS, including Bedrock fine-tuning, retrieval and evaluation. That helped most on the operational questions, where the trap is something you only learn by being caught by it, like an async job that says "accepted" long before the work is done.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I read questions
&lt;/h2&gt;

&lt;p&gt;Exam writers call the question text the stem: the scenario and the question above the answer options.&lt;/p&gt;

&lt;p&gt;Partway through preparing, I went back through every practice question I'd got wrong and sorted the mistakes. I expected to find gaps in my knowledge. Mostly I didn't. In most cases I already knew the rule that decided the question. I had read the question in the wrong order.&lt;/p&gt;

&lt;p&gt;The pattern was always the same. I read the stem, saw an option that sounded right, and then reread the stem looking for reasons to agree with it. The wrong options on this exam are written to match the most memorable phrase in the stem, so that habit picks the wrong answer again and again.&lt;/p&gt;

&lt;p&gt;So I replaced my checklist with four steps, done in order on every question.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Find the constraint sentence before reading any option
&lt;/h3&gt;

&lt;p&gt;Almost every stem has one sentence that acts as a filter rather than background: "with the least custom code", "without redeploying the API", "must not require client changes", "using a fully managed service". It's rarely the first sentence. Find it and hold it before your eyes reach the options, because once you've seen a plausible answer you'll read everything else to justify it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ask: what is this question forbidding?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. If options share an opening, compare the endings
&lt;/h3&gt;

&lt;p&gt;It's common for all four options to be identical for three or four lines and differ only in the final clause. Reading left to right means deciding on the part that's the same in every option, which by definition decides nothing. Ignore the shared part.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ask: where do these options actually disagree?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Eliminate on violation, never select on match
&lt;/h3&gt;

&lt;p&gt;Don't ask which option sounds right. Cross an option out the moment it breaks a constraint. A distractor usually satisfies one requirement very well and quietly fails another.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ask: which constraint does each option break?&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Check your pick against every constraint
&lt;/h3&gt;

&lt;p&gt;An option that fails one requirement is out, however good it looks on the others. If you can't say what ruled out each of the other options, you haven't finished the question.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ask: what ruled out each of the others?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;These four steps took me longer per question. They were also what moved my practice results. More on the cost of that in the pacing section at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed versus custom knowledge bases
&lt;/h2&gt;

&lt;p&gt;This one deserves its own section, because the same question shape can have opposite answers depending on a single phrase.&lt;/p&gt;

&lt;p&gt;Amazon Bedrock Knowledge Bases now come in two flavours. A &lt;strong&gt;custom&lt;/strong&gt; knowledge base is the one most people know: you choose the vector store (OpenSearch Serverless, Aurora, and so on), the parsing, the chunking, and how retrieval runs. A &lt;strong&gt;fully managed&lt;/strong&gt; knowledge base has the service manage those pieces for you. The two do not support the same APIs and options, and the exam leans on that.&lt;/p&gt;

&lt;p&gt;What changes between them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Custom knowledge base&lt;/th&gt;
&lt;th&gt;Managed knowledge base&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Generating an answer&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RetrieveAndGenerate&lt;/code&gt;, or &lt;code&gt;Retrieve&lt;/code&gt; and then your own model call&lt;/td&gt;
&lt;td&gt;Not &lt;code&gt;RetrieveAndGenerate&lt;/code&gt;. The API reference says to use &lt;code&gt;AgenticRetrieveStream&lt;/code&gt; or &lt;code&gt;Retrieve&lt;/code&gt; instead&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breaking a question into sub-questions&lt;/td&gt;
&lt;td&gt;Turn on query decomposition in &lt;code&gt;RetrieveAndGenerate&lt;/code&gt;, or decompose it yourself (for example in Lambda), call &lt;code&gt;Retrieve&lt;/code&gt; per sub-query and combine the results&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AgenticRetrieveStream&lt;/code&gt; plans sub-queries, retrieves until it judges the evidence sufficient, deduplicates chunks, cites sources and streams its planning as trace events. Managed only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chunking&lt;/td&gt;
&lt;td&gt;All strategies, including semantic&lt;/td&gt;
&lt;td&gt;Semantic chunking not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parsing&lt;/td&gt;
&lt;td&gt;Default, foundation model or Bedrock Data Automation parsing&lt;/td&gt;
&lt;td&gt;Smart parsing by default. Foundation model and Bedrock Data Automation parsing not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metadata filters&lt;/td&gt;
&lt;td&gt;Full set&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;startsWith&lt;/code&gt; and &lt;code&gt;stringContains&lt;/code&gt; not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Guardrails on agentic retrieval&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;td&gt;Blocking only. Masking not supported&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  A worked example
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The ask.&lt;/strong&gt; A team wants answers that break complex questions into sub-questions, show the planning steps, and cite sources, "using a fully managed knowledge base".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The tempting option.&lt;/strong&gt; &lt;code&gt;RetrieveAndGenerate&lt;/code&gt; with query decomposition switched on. It sounds perfect, because it really does decompose queries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's wrong.&lt;/strong&gt; &lt;code&gt;RetrieveAndGenerate&lt;/code&gt; is barred from managed knowledge bases, and it doesn't stream planning traces. The answer is &lt;code&gt;AgenticRetrieveStream&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What flips it.&lt;/strong&gt; Change one phrase so the team runs its own vector store, and the answer becomes decomposing the question yourself and calling &lt;code&gt;Retrieve&lt;/code&gt; per sub-query.&lt;/p&gt;

&lt;p&gt;When a knowledge base question appears, find out which flavour it is before you read the options.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each service is for
&lt;/h2&gt;

&lt;p&gt;Most wrong options on this exam are real services used for the wrong job. Knowing what each one is for, and what it isn't for, rules out options quickly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Converse&lt;/strong&gt; gives you one request shape across models. If the requirement is "swap models without changing the client code", it's Converse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;InvokeModel&lt;/strong&gt; takes a model-specific JSON body. It works, but every model family has its own format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ApplyGuardrail&lt;/strong&gt; checks text against a guardrail without calling a model at all. That's how you reject input before any model is invoked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CountTokens&lt;/strong&gt; estimates a request's token count before you send it. You don't need to call a small model to "estimate" it.&lt;/li&gt;
&lt;li&gt;Guardrails attach to Converse through &lt;code&gt;guardrailConfig&lt;/code&gt;. Header-based guardrails are for InvokeModel.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Retrieval
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retrieve&lt;/strong&gt; returns raw chunks with their metadata and relevance scores. Use it when you need to inspect or verify what was retrieved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RetrieveAndGenerate&lt;/strong&gt; returns a generated answer with citations. Use it on custom knowledge bases when you want the answer and where it came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid search&lt;/strong&gt; combines vector search with search over the raw text. It's only supported for Amazon RDS, OpenSearch Serverless and MongoDB vector stores that have a filterable text field. Otherwise the query falls back to semantic search. Part numbers, codes and IDs are the classic case that needs it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reranking&lt;/strong&gt; reorders results that were already retrieved. It improves ordering. It can't find documents the search missed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Chunking
&lt;/h3&gt;

&lt;p&gt;There are four strategies, and you choose one when you connect the data source. You can't change it afterwards: a different strategy means a new data source.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default / fixed size&lt;/strong&gt; splits into roughly equal chunks. With no configuration, it's 300 tokens with 20% overlap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hierarchical&lt;/strong&gt; matches on small child chunks and returns the larger parent chunk. Use it when you need precise matching but the answer needs the surrounding context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic&lt;/strong&gt; splits on meaning, so chunks don't break mid-thought. It fixes bad boundaries. It doesn't add surrounding context, and it isn't available on managed knowledge bases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;None&lt;/strong&gt; treats each file as one chunk. It only makes sense if you've already split your data into one record per file, which is custom preprocessing. A stem that says "without custom preprocessing" rules it out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Keeping data current
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sync (&lt;code&gt;StartIngestionJob&lt;/code&gt;)&lt;/strong&gt; crawls a data source and picks up new, changed and deleted content. Third-party connectors such as Confluence use this route.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct ingestion (&lt;code&gt;IngestKnowledgeBaseDocuments&lt;/code&gt;)&lt;/strong&gt; adds or updates individual documents without a sync. It's only available for S3 and custom data sources. On S3 sources, don't mix the two at the same time: changes indexed directly aren't written back to S3 and can be overwritten by the next sync.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Safety and privacy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Bedrock Guardrails&lt;/strong&gt; is the policy engine for prompts and responses, applied to both input and output. Its filters each do a different job:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filter&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Content filters&lt;/td&gt;
&lt;td&gt;Grade harmful content by category, including prompt attacks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Denied topics&lt;/td&gt;
&lt;td&gt;Block a subject outright, however politely it's asked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word filters&lt;/td&gt;
&lt;td&gt;Block specific words or phrases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sensitive information filters&lt;/td&gt;
&lt;td&gt;Block or mask PII and custom patterns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contextual grounding checks&lt;/td&gt;
&lt;td&gt;Catch answers not supported by the retrieved source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Automated Reasoning checks&lt;/td&gt;
&lt;td&gt;Validate answers against logical rules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The other services that come up next to it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Macie&lt;/strong&gt; finds sensitive data stored in S3. It doesn't inspect live prompts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS WAF&lt;/strong&gt; works at the HTTP layer. It can't judge what a prompt is trying to do.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Comprehend&lt;/strong&gt; detects PII and entities in text. It's useful as a pre-processing step, for example redacting before storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prompts and orchestration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Prompt Management&lt;/strong&gt; stores versioned prompt templates. Production should point at a published version, never the editable draft.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bedrock Flows&lt;/strong&gt; chains prompts, conditions and knowledge base lookups without much code. Flows also go to production as published versions behind an alias.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Functions Standard&lt;/strong&gt; suits long-running, auditable workflows (up to a year) with exactly-once execution. It's the one that supports waiting for a human callback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Functions Express&lt;/strong&gt; suits high-volume, short work (up to five minutes) with at-least-once execution. It doesn't support the callback or &lt;code&gt;.sync&lt;/code&gt; patterns, so it can't wait for a human approval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS AppConfig&lt;/strong&gt; changes configuration at runtime without redeploying. Switching models or providers without a redeploy is its classic use.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evaluation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic evaluation&lt;/strong&gt; runs without people and can serve as an unattended release gate. Each job takes at most 1,000 prompts, from a &lt;code&gt;.jsonl&lt;/code&gt; file in S3.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human evaluation&lt;/strong&gt; uses human workers, so it can't be unattended. It needs CORS configured on the S3 output bucket; automatic jobs don't.&lt;/li&gt;
&lt;li&gt;Starting an evaluation job returns a job ARN. That means it started, not that it passed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Operations and cost
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model invocation logging&lt;/strong&gt; captures the full request and response bodies, to CloudWatch Logs or S3 in the same account and Region. It only covers calls through the &lt;code&gt;bedrock-runtime&lt;/code&gt; endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch metrics&lt;/strong&gt; give counts, latency and token usage, but no prompt text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudTrail&lt;/strong&gt; records API calls, meaning who called what, without the content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provisioned Throughput&lt;/strong&gt; is for steady, predictable, committed volume. Prompt caching is for repeated prompt prefixes. They solve different problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost Explorer and Cost Anomaly Detection&lt;/strong&gt; cover spend analysis and alerting. Remember they exist; practice material rarely mentions them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The traps worth knowing
&lt;/h2&gt;

&lt;p&gt;These shapes came up again and again. Each comes with an example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading traps
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;One word apart.&lt;/strong&gt; Two options identical except for a single token: a different chunking strategy, &lt;code&gt;Retrieve&lt;/code&gt; against &lt;code&gt;RetrieveAndGenerate&lt;/code&gt;, Standard against Express. The answer is always in the part you haven't read yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Half of a multiple-response.&lt;/strong&gt; With no partial credit, one wrong pick zeroes the question. The correct picks usually cover different requirements or different layers, such as prevent plus detect, or real-time updates plus a scheduled rebuild. Two picks that solve the same requirement is a warning sign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The limit you didn't check.&lt;/strong&gt; A suspiciously specific number in the stem usually means a documented limit is being tested. 1,600 prompts won't fit in one automatic evaluation job. Lambda can't run past 15 minutes. An Express workflow stops at five minutes. Do the arithmetic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The setting you can't change later.&lt;/strong&gt; Chunking strategy is fixed once the data source is connected, and a Step Functions workflow type is fixed once the state machine is created. When a stem says the team "is creating the data source now", that sentence is there for a reason.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong tool for the job
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Monitoring offered for prevention.&lt;/strong&gt; The stem says prevent, block or reject, and an option offers a metric, alarm, log or dashboard. Those detect after the fact. Prevention means something in the request path: an IAM deny, a guardrail on the invocation, schema validation, an organisation policy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Right service, wrong place.&lt;/strong&gt; A real, sensible service used at a point where it can't act. Macie scans S3, so it never sees a live prompt. A reranker reorders chunks that were already retrieved, so it can't fix poor recall. Ask where in the request path each option actually runs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom build where a managed feature exists.&lt;/strong&gt; When the stem says "least operational overhead" or "least custom code", it is telling you to use the managed service, even when the custom Lambda version would clearly work. Glue Data Quality instead of a Lambda rule engine, Prompt Management instead of prompts in a Git repo, Bedrock Data Automation instead of a hand-built Textract and Comprehend pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixing the symptom.&lt;/strong&gt; If documents were parsed badly at ingestion, no retrieval setting will repair them. Fix parsing and re-sync. Hybrid search and reranking can't recover text that was never extracted properly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The wrong endpoint.&lt;/strong&gt; Bedrock has separate endpoints for the control plane (&lt;code&gt;bedrock&lt;/code&gt;), model inference (&lt;code&gt;bedrock-runtime&lt;/code&gt;), agent and knowledge base setup (&lt;code&gt;bedrock-agent&lt;/code&gt;) and agent and retrieval calls (&lt;code&gt;bedrock-agent-runtime&lt;/code&gt;). Private inference from a VPC needs the &lt;code&gt;bedrock-runtime&lt;/code&gt; interface endpoint. An option offering the &lt;code&gt;bedrock&lt;/code&gt; endpoint for inference sounds right and isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs, metrics and audit trails mixed up.&lt;/strong&gt; If the requirement is the actual prompt and response text, only model invocation logging has it. Metrics have numbers. CloudTrail has who called what. An option that looks for prompt text in CloudTrail is wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human versus automatic evaluation.&lt;/strong&gt; "Unattended" rules out human evaluation. "Humans must compare the outputs" rules out LLM-as-a-judge. And if the stem mentions CORS on the output bucket, it's quietly telling you the job uses human workers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrong state or scope
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Accepted is not complete.&lt;/strong&gt; An async call that returns a job ID or an "in progress" status has only started the work. Anything that treats submission as success skips the poll, the final status and the result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Draft against published version.&lt;/strong&gt; If the stem mentions production, stability, auditability or reproducibility, a mutable draft is already ruled out. Prompts, flows and guardrails all follow this rule.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real parameter on the wrong API.&lt;/strong&gt; Sampling parameters such as temperature on an embeddings call, or &lt;code&gt;messages&lt;/code&gt; added to a call that already uses a managed prompt template. Check that each parameter belongs to the API named in the option.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoped too narrowly.&lt;/strong&gt; Cross-Region inference routes requests to several Regions, so IAM and any organisation policies must allow the model in every destination Region of the profile. Scoping permissions to the calling Region looks tighter and fails at runtime with access denied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Topics I'd make sure of
&lt;/h2&gt;

&lt;p&gt;This is a study map built from the exam guide's tasks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Domain 1: Foundation Model Integration, Data Management, and Compliance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Choosing foundation models and switching between them without code changes&lt;/li&gt;
&lt;li&gt;Resilience, including cross-Region inference and graceful degradation&lt;/li&gt;
&lt;li&gt;Data validation before ingestion&lt;/li&gt;
&lt;li&gt;Vector store choices and Bedrock Knowledge Bases, including managed versus custom, chunking strategies and metadata filtering&lt;/li&gt;
&lt;li&gt;Retrieval design: hybrid search, reranking and query decomposition&lt;/li&gt;
&lt;li&gt;Prompt engineering and Prompt Management with versioning&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Domain 2: Implementation and Integration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bedrock Agents and tool use, action groups and orchestration&lt;/li&gt;
&lt;li&gt;Deployment options: on-demand, provisioned throughput and custom model deployment&lt;/li&gt;
&lt;li&gt;Integration patterns with API Gateway, Lambda, Step Functions and event-driven designs&lt;/li&gt;
&lt;li&gt;The Bedrock runtime APIs, including Converse against InvokeModel, streaming, and what each request accepts&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Domain 3: AI Safety, Security, and Governance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bedrock Guardrails in detail: every filter type, where guardrails apply, and versioning them across accounts&lt;/li&gt;
&lt;li&gt;Prompt injection defence&lt;/li&gt;
&lt;li&gt;Encryption, VPC endpoints and data residency&lt;/li&gt;
&lt;li&gt;Governance: logging, lineage and audit trails&lt;/li&gt;
&lt;li&gt;Responsible AI principles and how AWS expects you to put them into practice&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Domain 4: Operational Efficiency and Optimization for GenAI Applications
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Token cost and its drivers&lt;/li&gt;
&lt;li&gt;Caching&lt;/li&gt;
&lt;li&gt;Choosing between on-demand, batch and provisioned capacity&lt;/li&gt;
&lt;li&gt;Latency tuning&lt;/li&gt;
&lt;li&gt;Monitoring with CloudWatch and invocation logging, and cost tracking with Cost Explorer and Cost Anomaly Detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Domain 5: Testing, Validation, and Troubleshooting
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Bedrock model evaluation, both automatic and with a judge model, plus human evaluation&lt;/li&gt;
&lt;li&gt;RAG evaluation, separating retrieval quality from answer quality&lt;/li&gt;
&lt;li&gt;Troubleshooting retrieval, throttling, timeouts and quota problems&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pacing: what question 55 taught me
&lt;/h2&gt;

&lt;p&gt;180 minutes for 75 questions sounds generous. It isn't, once you start reading carefully.&lt;/p&gt;

&lt;p&gt;I took my time on every question. I found the constraint sentence, compared the endings of the options, eliminated on violation, and checked my pick against every condition before moving on. That's what this exam rewards, and I'd do it again. But by question 55 I had about 30 minutes left for the last 20 questions, and those had to move a lot faster than the first 55.&lt;/p&gt;

&lt;p&gt;What I'd do differently is set checkpoints before starting:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Aim to be at&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;55 minutes&lt;/td&gt;
&lt;td&gt;Question 25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;110 minutes&lt;/td&gt;
&lt;td&gt;Question 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;110 to 180 minutes&lt;/td&gt;
&lt;td&gt;The last 25 questions and a short review&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That keeps the careful reading and still leaves room at the end. If one question is eating your time, make your best elimination, flag it and move on. A blank scores the same as a wrong answer, and a guess costs nothing.&lt;/p&gt;

&lt;p&gt;Read slowly, but keep an eye on the clock.&lt;/p&gt;

&lt;p&gt;If you're preparing for AIP-C01 and want to compare notes, leave a comment.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>certification</category>
      <category>genai</category>
      <category>bedrock</category>
    </item>
    <item>
      <title>My Eval Passed Because the Model Had Already Seen the Answers</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Thu, 01 Oct 2026 14:37:34 +0000</pubDate>
      <link>https://dev.to/aws-builders/my-eval-passed-because-the-model-had-already-seen-the-answers-4ca8</link>
      <guid>https://dev.to/aws-builders/my-eval-passed-because-the-model-had-already-seen-the-answers-4ca8</guid>
      <description>&lt;h2&gt;
  
  
  Trap one: the exam was in the textbook
&lt;/h2&gt;

&lt;p&gt;The first time my eval battery came back green, I was pleased with myself. The result was worthless, and it took me a while to work out why.&lt;/p&gt;

&lt;p&gt;An eval battery is the test suite that decides whether a language model is good enough to put in front of real users. Mine holds 50 test cases, grouped into 23 categories that each cover one behaviour the model has to get right. It runs before anything ships and reports a flat pass or fail rather than a score.&lt;/p&gt;

&lt;p&gt;Here is what one of those test cases looks like. The rows in my battery carry product specifics, so this is the same schema rewritten in a neutral domain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"category_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Refuses to invent a version number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"which version of the SDK added retry support?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pass_criterion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"at least one draft says it does not know the version rather than guessing one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"len_cap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"emoji_policy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pass criterion is prose rather than a pattern. Code is good at structure and bad at judgement, and I have the scar tissue to prove it. Every time I have tried to express a quality rule as a pattern match, the check ended up rejecting work that was actually fine.&lt;/p&gt;

&lt;p&gt;Now look at that &lt;code&gt;source&lt;/code&gt; field again, because that is where I went wrong.&lt;/p&gt;

&lt;p&gt;I had built my training data by rewriting the very source material that was in the battery. Side by side, the problem is hard to miss:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;training&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;pair&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"which version of the SDK added retry support?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"target"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"I do not know the exact version. Check the changelog rather than trusting me on it."&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;battery&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;row,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;same&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"B1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"which version of the SDK added retry support?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="nl"&gt;"pass_criterion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"at least one draft says it does not know the version rather than guessing one"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The model had seen that exact input during training with a good answer attached. When the battery asked it back, it was not generalising. It was reciting.&lt;/p&gt;

&lt;p&gt;This is the worst class of bug in an eval, because it fails silently in the direction of good news. A broken eval that reports failure gets fixed the same afternoon. A broken eval that reports success gets celebrated and shipped.&lt;/p&gt;

&lt;p&gt;The fix went into the dataset builder rather than onto a checklist, because a checklist is a thing you forget at 2am. It normalises and tokenises every source, then drops any training pair whose source matches a test source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 1. exact: drop any training pair whose source is also a test source
&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dropped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;exclude_test_sources&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;train&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. fuzzy: catch the paraphrases that survived step 1
&lt;/span&gt;&lt;span class="n"&gt;leaks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pair&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;train&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;jaccard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;test_sources&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exact matching was not enough on its own, because a lightly edited restatement of a test input is still a memorised test input. Step two tokenises both sides, takes the highest word overlap against any test source, and flags anything at 60% or above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;training:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"what version of the SDK got retry support?"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;battery:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s2"&gt;"which version of the SDK added retry support?"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;shared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;words:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;version,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sdk,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;retry,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;support&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;words&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;across&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;both&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;jaccard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="err"&gt;flagged&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two questions sharing 60% of their words are, in practice, the same question. That is where the threshold came from.&lt;/p&gt;

&lt;p&gt;Both results now print in the data audit report as hard checks, with the required value stated next to them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;battery fuzzy leaks (j&amp;gt;=.6) : 0  (must be 0)
proven and battery (exact)  : 0  (must be 0, hold-out disjoint)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is my own output, so the names are mine: "battery" is the test set and "proven" is one of my two training pools. Three consecutive dataset builds have reported 0 on both. That is the only reason I trust anything the battery says now.&lt;/p&gt;

&lt;p&gt;One corollary came out of this that cost me a second time. A baseline is only valid against the identical battery version. Change the exam and every previous score becomes uncomparable, so the incumbent model has to be re-run on the new exam before anyone claims an improvement. There is a pair of scores in my own notes that I cannot use in this article for exactly that reason. One was recorded three weeks before a battery change, the other after it, and I have no way to prove they measure the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap two: one run is not a measurement
&lt;/h2&gt;

&lt;p&gt;With the leak closed I finally had an exam the model had genuinely not seen. So I ran it.&lt;/p&gt;

&lt;p&gt;Before the number means anything, here is what one point costs. Each row samples the model six times. All six drafts go to a judge model with that row's criterion and a fixed rubric, and the judge returns scores, a ranking, a pass boolean, and a list of which drafts tripped each structural rule. The row is then scored against 12 assertions, with the specifics swapped out again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;checks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;criterion_met&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;     &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_tell_a_top2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;_hit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tell_a_drafts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_tell_b_top2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;_hit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tell_b_drafts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;no_tell_c_top2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;_hit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tell_c_drafts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;# ...six more of the same shape, one per failure mode
&lt;/span&gt;    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;len_ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;median_len&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;len_cap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;emoji_ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;emoji_ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A row scores a point only if all 12 pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ranking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;median_len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;len_cap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the ship gate is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;all_pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row_results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no partial credit and no passing percentage. That was deliberate. A scorecard reading 44 out of 50 invites you to round it up to fine, whereas a flat fail makes someone open it and say out loud which rows they are willing to accept.&lt;/p&gt;

&lt;p&gt;One more detail matters for everything that follows. The judge runs at temperature 0 and sampling runs at 0.9, so what comes next is the model's own variance rather than a wobbly judge.&lt;/p&gt;

&lt;p&gt;I ran the battery three times against the same model, on consecutive days, with nothing changed between runs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;31 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;21 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;31 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Ten rows of spread. Twenty percent of the battery, on a model nobody touched.&lt;/p&gt;

&lt;p&gt;That matters because of what it does to comparisons. If a model can swing ten rows against itself, a ten-row gap between two different models means nothing on its own. I could have taken the worst run of an old model and the best run of a new one and reported a ten-row improvement in complete good faith. Reverse the pairing and the same two models show a ten-row regression. Neither number would have been real.&lt;/p&gt;

&lt;p&gt;So, precisely: three runs of one model, 50 rows, six samples per row. Enough to show that single-run comparisons on this battery are unsafe. Not enough to say how unsafe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap three: a steady total can hide a moving model
&lt;/h2&gt;

&lt;p&gt;So I started running replicates. And a newer model looked like the problem had gone away:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;27 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;28 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;27 / 50&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One row of spread across three runs. I nearly wrote that down as a more consistent model and moved on.&lt;/p&gt;

&lt;p&gt;It is tempting to read that table against the previous one. The older model spanned 21 to 31 across its three runs, the newer one 27 to 28. Those ranges overlap, so this battery cannot tell the two models apart, and picking a winner from them would be the exact mistake the last section was about.&lt;/p&gt;

&lt;p&gt;What those three runs did let me do was look underneath a total that barely moved. So I read the per-category lines instead. Across those three identical runs, 19 of the 23 categories changed their score at least once. Five of them:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Run 1&lt;/th&gt;
&lt;th&gt;Run 2&lt;/th&gt;
&lt;th&gt;Run 3&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four categories held still, and two of those four were ones the model failed in all three runs. Reliable failure is still information, and it was the only kind this battery gave me that I could trust without replicating it first.&lt;/p&gt;

&lt;p&gt;At row level it is starker. &lt;strong&gt;29 of the 50 rows changed verdict between identical runs.&lt;/strong&gt; Only 21 rows gave the same answer all three times. The total held steady because the errors cancelled out, not because the model was behaving consistently. Had I run the battery once and shipped on the number, I would have been shipping on a coin flip wearing a lab coat.&lt;/p&gt;

&lt;p&gt;That raised an obvious question, so I went and counted which assertion was actually failing on those 29 unstable rows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Failing check&lt;/th&gt;
&lt;th&gt;Share of 48 failures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;len_ok&lt;/code&gt;, the length cap&lt;/td&gt;
&lt;td&gt;34, or 71%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;the top-two structural checks&lt;/td&gt;
&lt;td&gt;11, or 23%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;criterion_met&lt;/code&gt;, the judge's own verdict&lt;/td&gt;
&lt;td&gt;1, or 2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;everything else&lt;/td&gt;
&lt;td&gt;2, or 4%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A row can trip more than one check at once, so those are failure instances rather than rows.&lt;/p&gt;

&lt;p&gt;I had assumed the churn was the judge changing its mind about quality. It was not. It was &lt;strong&gt;output length wobbling across runs and crossing a fixed cap.&lt;/strong&gt; The judgement checks were comparatively stable, and across all 29 unstable rows the judge's own pass decision accounted for exactly one failure.&lt;/p&gt;

&lt;p&gt;That is a more useful finding than the one I expected, because length is a knob you set at generation time rather than a property of the model's taste. A mechanical variable was dominating a scorecard I was using to make quality decisions.&lt;/p&gt;

&lt;p&gt;This is the part that changed how I read every scorecard since. A stable aggregate is not evidence of a stable system. It can equally be evidence that you are averaging over enough noisy measurements for the noise to cancel out. The number is not wrong. It is answering a question you did not ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Replicates, always.&lt;/strong&gt; Three runs minimum, and every row reported as a count out of three rather than as a pass or a fail. A row that passes twice out of three is a different object from one that passes three times out of three, and collapsing both to PASS throws away the only information that mattered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Length reported separately from quality.&lt;/strong&gt; Given that the length cap drove 71% of the instability, scoring it alongside the judgement checks let one mechanical variable swamp everything else. It now gets reported on its own line, so a quality regression cannot hide behind a length change or the reverse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Category sizes that can carry a decision.&lt;/strong&gt; One category in my battery has 2 rows. Another has 1. When a retrain is aimed squarely at a behaviour that only 2 of 50 rows test, the battery cannot see whether the retrain worked. The signal is smaller than the noise before a single GPU hour is spent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A human comparison when the battery cannot decide.&lt;/strong&gt; On one occasion I took the nine rows two models disagreed on, ran each of them three times, and scored the results with the length check excluded. Both models landed on exactly 23 of those 27 results. A blind side by side comparison on real inputs settled it in about twenty minutes. The battery catches regressions on things I have already decided matter. It is not an oracle, and when it returns a tie, the honest reading is that it has no opinion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A gate with no partial credit.&lt;/strong&gt; The scorecard reports a flat fail rather than a percentage, which forces someone to name the failing rows and accept them one at a time instead of rounding 44 out of 50 up to fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part worth taking away
&lt;/h2&gt;

&lt;p&gt;Building the battery was the easy half. Plenty of tutorials will show you how to write assertions and wire up a judge.&lt;/p&gt;

&lt;p&gt;The hard half was finding out what my battery could not see. It could not see a memorised answer until I checked source overlap in the builder. It could not see its own run to run variance until I ran it three times. It could not see that a rock steady total was hiding 29 changed rows until I stopped reading the total. And I could not see that the churn was mostly length until I counted which assertion was failing, rather than assuming I already knew.&lt;/p&gt;

&lt;p&gt;So: run your eval twice before you trust it once. If the two runs disagree, you have learned something more useful than either number.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>I Swept All 33 Bedrock Regions So You Don't Have To</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Tue, 08 Sep 2026 18:11:46 +0000</pubDate>
      <link>https://dev.to/aws-builders/i-swept-all-33-bedrock-regions-so-you-dont-have-to-5527</link>
      <guid>https://dev.to/aws-builders/i-swept-all-33-bedrock-regions-so-you-dont-have-to-5527</guid>
      <description>&lt;p&gt;The Amazon Bedrock pricing page publishes a training price for the Meta model you almost certainly are not fine-tuning.&lt;/p&gt;

&lt;p&gt;Here is the customization pricing on the page, as of 8 September 2026:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Published training price&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Llama 2 Pretrained 13B&lt;/td&gt;
&lt;td&gt;$1.49 per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Llama 2 Pretrained 70B&lt;/td&gt;
&lt;td&gt;$7.99 per 1M tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cohere Command&lt;/td&gt;
&lt;td&gt;$0.004 per 1,000 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Titan Image Generator&lt;/td&gt;
&lt;td&gt;$0.005 per image seen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gpt-oss-20b&lt;/td&gt;
&lt;td&gt;$80 per training hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Qwen3 32B&lt;/td&gt;
&lt;td&gt;$80 per training hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Llama 3.1, Llama 3.3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Other providers are covered. Two models are even priced by the hour. &lt;strong&gt;The only Meta model with a published training price is Llama 2&lt;/strong&gt;, and the Llama you would actually fine-tune today is not on the list.&lt;/p&gt;

&lt;p&gt;I have a fine-tuned Llama 3.3 70B running in production on Bedrock, and a line item on my AWS bill saying it cost real money to train. So the capability exists, it bills, and I could not find its price published anywhere.&lt;/p&gt;

&lt;p&gt;A caution while you are here: I have seen third-party guides quote "$0.00799 per 1,000 tokens" as the Llama 3.3 70B training rate. That is the Llama 2 70B figure restated and applied to a different model. If you are budgeting, price it from your own first job rather than from a number someone inferred.&lt;/p&gt;

&lt;p&gt;That gap turned out to be the least surprising thing I learned. This article is the map I wish I had before I started: which regions can actually do this, what the documented way around it says versus what happened when I took it, and the one piece of job state that decides whether a stalled job is costing you money or nothing at all.&lt;/p&gt;

&lt;p&gt;Everything below is either something I measured or something AWS documents, and I have labelled which. Where the two disagree, you get both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: the sweep
&lt;/h2&gt;

&lt;p&gt;I did not want to work from a documentation table. The Bedrock catalogue varies by region more than people expect, and a page describing 33 regions is a hard thing to keep current. So I asked the API directly, in every region the SDK knows about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;futures&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;botocore.config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connect_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_attempts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;regions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_available_regions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bedrock&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_foundation_models&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;byCustomizationType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FINE_TUNING&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelSummaries&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ERR &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ThreadPoolExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_workers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regions&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3-3-70b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;byCustomizationType="FINE_TUNING"&lt;/code&gt; is the important part. It returns only the models you can actually train, not the much longer list you can invoke.&lt;/p&gt;

&lt;p&gt;33 regions. Here is what came back.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Regions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Can fine-tune Llama 3.3 70B&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1&lt;/strong&gt; (us-west-2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answered, some tunable models, no Llama&lt;/td&gt;
&lt;td&gt;2 (us-east-1, eu-west-2)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Answered, zero tunable models&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Opt-in regions, not enabled on my account&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;One region can fine-tune Llama 3.3 70B.&lt;/strong&gt; Out of the 33 the SDK lists, and out of the 20 that gave me a real answer. Not a short list. One.&lt;/p&gt;

&lt;p&gt;An honest caveat, because the difference matters. Those 13 are not a fine-tuning answer. They returned &lt;code&gt;UnrecognizedClientException&lt;/code&gt;, which is what you get when you call an opt-in region your account has never enabled. I cannot rule them out from this account, and neither can you from yours unless you have opted in. Twenty regions gave a real answer.&lt;/p&gt;

&lt;p&gt;The two regions that answered with something other than zero are more interesting than the seventeen that answered zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;us-west-2&lt;/strong&gt;, five tunable models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amazon.titan-embed-image-v1
anthropic.claude-3-haiku-20240307-v1
meta.llama3-1-8b-instruct-v1
meta.llama3-1-70b-instruct-v1
meta.llama3-3-70b-instruct-v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;us-east-1&lt;/strong&gt;, six tunable models:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;amazon.nova-micro-v1
amazon.nova-lite-v1
amazon.nova-pro-v1
amazon.nova-2-lite-v1
amazon.nova-canvas-v1
amazon.titan-embed-image-v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that second list again. In the region most people default to, &lt;strong&gt;every model you can fine-tune is an Amazon model.&lt;/strong&gt; No Llama. No Anthropic. If you want to customise anything that is not Amazon's own, us-east-1 cannot do it.&lt;/p&gt;

&lt;p&gt;The console agrees. Opening Create Fine-tuning job in us-east-1 on 8 September 2026, the model picker offers one category, "Serverless model providers", and one provider under it: Amazon. The six models it lists are the same six the API returns.&lt;/p&gt;

&lt;p&gt;And eu-west-2, London, where a lot of UK and EU workloads want to sit for latency or residency reasons, has exactly one tunable model. It is not a Llama.&lt;/p&gt;

&lt;p&gt;This has been stable. I first ran the sweep on 1 August 2026 and re-ran it on 8 September 2026 before publishing. Same answer both times. That is five weeks, not five years, so re-run it yourself rather than trusting my table. The snippet above takes about a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: the escape hatch, and what happened when I tried it
&lt;/h2&gt;

&lt;p&gt;The obvious way out is to train the model somewhere else and bring the weights in. Bedrock has Custom Model Import for exactly that, and on paper it fits.&lt;/p&gt;

&lt;p&gt;The documentation is clear and it is worth quoting accurately, because my experience differed from it and I want you to have both.&lt;/p&gt;

&lt;p&gt;Custom Model Import lists &lt;strong&gt;Llama 3.3&lt;/strong&gt; among its supported architectures. It states the ceiling in terms of weight size rather than parameter count: &lt;strong&gt;under 200GB for text models&lt;/strong&gt;, with a maximum context length below 128K. It is available in &lt;strong&gt;eu-central-1, us-east-1, us-east-2 and us-west-2&lt;/strong&gt;, which is four regions to native customization's one. AWS has published a walkthrough of importing DeepSeek-R1-Distill-Llama-70B through it.&lt;/p&gt;

&lt;p&gt;By that description, importing a fine-tuned Llama 3.3 70B should work. My artifacts were 141GB in bf16, comfortably inside the documented 200GB.&lt;/p&gt;

&lt;p&gt;The import failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;too large to fit on available hardware
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole error. It does not say which limit was hit, whether the constraint was my artifacts, the region, or capacity at that moment. Searching it turns up other people receiving the same generic message on far smaller models, including an 8B, which suggests it is a catch-all rather than a statement about 70B specifically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So the honest position is narrower than "it cannot be done."&lt;/strong&gt; The documented path supports the architecture and the size. It did not complete for me, on my artifacts, in my account, on the day I tried, and the error was not specific enough to tell me why. Someone else may well succeed with it, and AWS's own material suggests they do.&lt;/p&gt;

&lt;p&gt;What that means practically, if you are planning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Native customization is the constrained path.&lt;/strong&gt; One region for Llama 70B, and no choice about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import is the less constrained path on paper&lt;/strong&gt;, including into eu-central-1, which matters if EU residency is a hard requirement for you. Try it before you conclude anything from my result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget for the possibility that it does not complete&lt;/strong&gt;, and for an error that will not tell you why.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For my own build, native customization is what worked, so that is where the model lives, and the application runs in a different region from the custom model deployment. To be precise about which half of that was forced: the customization and its deployment had to be in us-west-2. Where the application runs was my choice, and I could move it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: InProgress means two completely different things
&lt;/h2&gt;

&lt;p&gt;This is the part I have not found documented anywhere, and it is the one that costs money.&lt;/p&gt;

&lt;p&gt;Launch a customization job and poll it. The status reads &lt;code&gt;InProgress&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That tells you nothing useful, because &lt;code&gt;InProgress&lt;/code&gt; covers two states that are not remotely the same:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The job is sitting in a capacity queue waiting for a GPU. &lt;strong&gt;No trainer has started. You are being charged nothing.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The job is training. &lt;strong&gt;You are burning GPU time.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;From the outside these look identical. Same status, same API, same console. The only thing that separates them is nested one level down:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_model_customization_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# 'InProgress'   &amp;lt;- tells you nothing
&lt;/span&gt;
&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusDetails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trainingDetails&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# 'NotStarted'   &amp;lt;- queued, free to stop
# 'InProgress'   &amp;lt;- training, stopping now pays for nothing
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;statusDetails.trainingDetails.status&lt;/code&gt; is the only honest signal.&lt;/strong&gt; Check it before you stop anything.&lt;/p&gt;

&lt;p&gt;Why it matters in cash terms. Over one week of capacity contention I stopped five jobs. Four had never left the queue.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jobs stopped&lt;/th&gt;
&lt;th&gt;Reached the trainer&lt;/th&gt;
&lt;th&gt;Billed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.00&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$29.89&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four jobs, each killed after many hours, cost nothing at all. Queue time is free. Had I believed the top-level status, I would have assumed all five cost me something and drawn exactly the wrong conclusion about how expensive iteration is.&lt;/p&gt;

&lt;p&gt;This is consistent with what AWS documents, once you read it precisely. The guidance on stopping a job says Bedrock charges for the tokens it used to train the model before you stopped it. If training never started, no tokens were used, and there is nothing to charge for. The documentation and the invoice agree. What neither tells you is which of the two states your job is in, and that is the gap &lt;code&gt;trainingDetails&lt;/code&gt; fills.&lt;/p&gt;

&lt;p&gt;One more thing that trips people up here: &lt;strong&gt;validation completing does not mean training has started.&lt;/strong&gt; Validation proves your S3 permissions and your data schema are fine. It says nothing about whether a GPU has been allocated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 4: the threshold I invented
&lt;/h2&gt;

&lt;p&gt;Here is the mistake, because it is more useful than the finding.&lt;/p&gt;

&lt;p&gt;Jobs were stalling in the queue. I went back through my own job history and worked out that the longest queue wait ever followed by successful training was about 11 hours. So I adopted a rule: past 11 hours the job is hung, kill it and relaunch.&lt;/p&gt;

&lt;p&gt;That rule was garbage, and it took me a while to see why.&lt;/p&gt;

&lt;p&gt;Every job in my history that had waited longer than 11 hours had been &lt;strong&gt;killed by me before it could recover.&lt;/strong&gt; The number did not measure Bedrock's behaviour. It measured my patience. I then used it to justify killing the next job, which fed the same number back into the same conclusion.&lt;/p&gt;

&lt;p&gt;The data point that broke it: one job waited &lt;strong&gt;23 hours and 15 minutes&lt;/strong&gt; in the queue, then trained normally and produced a working model.&lt;/p&gt;

&lt;p&gt;An earlier job had been stopped at 17 hours 31 minutes on the strength of a threshold that did not exist. It cost $0.00, because it had never left the queue, but it cost most of a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The queue has no published upper bound that I could find.&lt;/strong&gt; The documented threshold applies to a different state. AWS's troubleshooting guidance says training time runs "between 3-4 hours, up to 24 hours, depending on configuration and traffic", and that you should contact Support if a job has been &lt;em&gt;in Training&lt;/em&gt; for more than 24 hours. In Training, not in queue. The phrase "depending on configuration and traffic" is doing real work there: capacity is shared, and the wait before training is not the thing being bounded.&lt;/p&gt;

&lt;p&gt;The correct response to a stalled customization job, given that queue time is free and every relaunch goes to the back of the queue: &lt;strong&gt;wait.&lt;/strong&gt; Impatience has a real cost here. Patience has none.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5: the things that will bite you next
&lt;/h2&gt;

&lt;p&gt;Four more, briefly, all learned the expensive way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The schema is per model family. The limits are not.&lt;/strong&gt; Llama 3.3 takes the Converse format, with &lt;code&gt;schemaVersion&lt;/code&gt;, &lt;code&gt;system[]&lt;/code&gt; and &lt;code&gt;messages[]&lt;/code&gt;. Llama 3.1 rejects it outright, with &lt;code&gt;ValidationException: Unable to parse S3 file due to invalid data schema/format&lt;/code&gt;, and wants flat &lt;code&gt;{"prompt": ..., "completion": ...}&lt;/code&gt; instead. Both share the same 16,000 token ceiling, the same 100 to 10,000 record range, the same epoch and learning rate bounds. Matching limits are not matching schemas, and I lost a launch to assuming otherwise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;prompt/completion has no system field.&lt;/strong&gt; Move from 3.3 to 3.1 and your system prompt has to be prepended into every training record, then reproduced byte for byte at inference. Get one character wrong later and the model quietly degrades, with no error anywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your real concurrency limit may be smaller than you think.&lt;/strong&gt; The quota that binds is &lt;code&gt;Custom models with a creating status per account&lt;/code&gt;, which on my account is &lt;strong&gt;2&lt;/strong&gt;. Not the scheduled-customization-jobs quota, which on my account is 10 and looks like the constraint until you hit the other one. Check both in Service Quotas rather than assuming my numbers are the defaults.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage is per model per month.&lt;/strong&gt; On my bill, a retained rollback model comes to about $1.95 a month to sit there. Cheap, and worth it for the rollback, but not free, and easy to accumulate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision tree, compressed
&lt;/h2&gt;

&lt;p&gt;If you are considering fine-tuning a Llama on Bedrock, the honest version is short.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;For native customization, the region is decided for you.&lt;/strong&gt; us-west-2 or nothing, for Llama 70B. If you go that route, design the cross-region call in from the start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Try Custom Model Import before you accept that.&lt;/strong&gt; It documents support for Llama 3.3, a 200GB text ceiling and four regions including eu-central-1. Mine failed with a generic hardware error inside the documented limit, so budget for that outcome, but do not take my result as the rule.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check &lt;code&gt;trainingDetails&lt;/code&gt;, never the top-level status,&lt;/strong&gt; before you stop a job or conclude anything about cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When it stalls, wait.&lt;/strong&gt; Queue time is free, relaunching is not, and any hang threshold you derive from your own history is measuring you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the schema doc for your exact model version,&lt;/strong&gt; not the family. The limits will match and the format will not.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of it sits in one place, which is why it is here. The pricing page still says Llama 2.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>amazonbedrock</category>
    </item>
    <item>
      <title>Don't Fine-Tune. Unless You Can Answer These Three Questions.</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Tue, 25 Aug 2026 19:21:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/dont-fine-tune-unless-you-can-answer-these-three-questions-1k8e</link>
      <guid>https://dev.to/aws-builders/dont-fine-tune-unless-you-can-answer-these-three-questions-1k8e</guid>
      <description>&lt;p&gt;Twice now I have gone looking for a retrain and found the problem somewhere else.&lt;/p&gt;

&lt;p&gt;The first time, a third of my output had collapsed into one template. Before spending anything I counted the pattern in my training data. 5 occurrences in 1,610 examples. Then I counted it in the output. 36%. The cause was a single example I had hardcoded into my own prompt months earlier, and the model was copying it.&lt;/p&gt;

&lt;p&gt;The second time, quality had gone flat across the board. The system prompt had grown to 224,833 characters, roughly 56,000 tokens on every call, and ninety percent of it was accumulated rules about how the output should sound. The model was doing exactly what I had asked it to do, which was avoid things.&lt;/p&gt;

&lt;p&gt;Neither needed new weights.&lt;/p&gt;

&lt;p&gt;That is the pattern I would put in front of anyone about to fine-tune. It is the most expensive way to discover you had a prompt bug. About $30 and a five-hour job per run on a 70B model, and the compute is the small part. The real cost is that you now have two variables moving and no clean way to tell which one broke.&lt;/p&gt;

&lt;p&gt;So, three questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Is this a knowledge problem or an identity problem?
&lt;/h2&gt;

&lt;p&gt;Fine-tuning does not teach a model facts. It shifts a distribution.&lt;/p&gt;

&lt;p&gt;If the model needs to know things it currently does not, your product details, your documentation, anything that was true last week and false today, that is retrieval. Training knowledge into weights means retraining every time the knowledge changes, which is a subscription you did not mean to sign.&lt;/p&gt;

&lt;p&gt;Fine-tuning is for behaviour that stays put. How it writes. What register it falls back to. What it does when the input is ambiguous.&lt;/p&gt;

&lt;p&gt;The test I use: if the right answer changes when the underlying data changes, it belongs in retrieval.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Have you stripped the prompt back first?
&lt;/h2&gt;

&lt;p&gt;Most requests to fine-tune are prompt bloat wearing a disguise.&lt;/p&gt;

&lt;p&gt;Prompts accrete. Something goes wrong, you add a rule, it improves, and nothing ever gets removed, because removing a rule feels like inviting the bug back. Six months later two hundred lines of prohibitions are competing with two lines describing the actual job, and the model attends across all of it.&lt;/p&gt;

&lt;p&gt;Before you price a training run, cut the prompt to the smallest thing that states the task, and measure again. If the output improves, you never had a weights problem.&lt;/p&gt;

&lt;p&gt;This is uncomfortable, because it means deleting work you were proud of. Do it anyway. You cannot diagnose a system you have only ever added to.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Can you actually produce the data?
&lt;/h2&gt;

&lt;p&gt;This is the question that stops people, and it is where most of the effort goes.&lt;/p&gt;

&lt;p&gt;Everyone has data. Almost nobody has a thousand or more curated examples of the exact behaviour they want, cleaned, and is prepared to keep cleaning them.&lt;/p&gt;

&lt;p&gt;My last build started from 1,418 labelled examples and kept 884 after cleaning. The final training set reached 1,610 rows once other curated sources were added. Here is what got dropped from that base pool, and none of it was optional.&lt;/p&gt;

&lt;p&gt;Rhetorical tics I did not want the model to learn: 107 examples dropped for one construction alone, plus smaller counts for two others. If a pattern sits in your training data at any volume, you are teaching it.&lt;/p&gt;

&lt;p&gt;Length and register discipline: 302 dropped for being the wrong length for their category. A model trained on mixed lengths produces mixed lengths.&lt;/p&gt;

&lt;p&gt;Opener variety: 45 dropped because more than two examples shared the same opening three words. Without that cap the model finds one opening it likes and reuses it everywhere.&lt;/p&gt;

&lt;p&gt;Then there was the part I did not plan for.&lt;/p&gt;

&lt;h3&gt;
  
  
  83 examples were being deleted on every build
&lt;/h3&gt;

&lt;p&gt;The build had a decontamination step. It removed hand-approved examples from one source file, on the assumption they had already been copied into a curated file elsewhere.&lt;/p&gt;

&lt;p&gt;Nothing had ever copied them.&lt;/p&gt;

&lt;p&gt;83 examples were dropped silently at build time, on every run, for months. They were disproportionately the best ones, the examples carrying concrete specifics rather than general statements, because those were exactly the ones I had approved by hand in the first place.&lt;/p&gt;

&lt;p&gt;A row that never arrives leaves no trace. Nothing in a training run tells you what it did not receive.&lt;/p&gt;

&lt;p&gt;So there was no error. The row counts looked plausible. The model trained, the evaluation ran, and the result was slightly worse than it should have been in a way no single test could isolate.&lt;/p&gt;

&lt;p&gt;I found it by reading the builder line by line.&lt;/p&gt;

&lt;p&gt;That is not really a lesson about checking your pipeline. A training pipeline fails quietly by design. Code that drops rows produces a smaller number, and a smaller number still looks like a number. So print what you dropped and why, broken down by reason, on every single build, and then read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bar
&lt;/h2&gt;

&lt;p&gt;Answer all three and fine-tuning is a reasonable thing to do, and it works. Voice in particular is something you can instruct a model toward but cannot make native to it, and that gap is real.&lt;/p&gt;

&lt;p&gt;You will also need an evaluation set that can fail, held out and genuinely separate from anything you trained on, and a rollback path that restores the previous model together with its system prompt. Those are their own articles, and they are the difference between shipping a fine-tune and gambling on one.&lt;/p&gt;

&lt;p&gt;But answer the three questions first. Two of them cost nothing except honesty.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llmops</category>
    </item>
    <item>
      <title>Every Rule I Added Made It Worse: How Prompt Bloat Killed My Voice</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Fri, 14 Aug 2026 14:11:21 +0000</pubDate>
      <link>https://dev.to/aws-builders/every-rule-i-added-made-it-worse-how-prompt-bloat-killed-my-voice-3ekd</link>
      <guid>https://dev.to/aws-builders/every-rule-i-added-made-it-worse-how-prompt-bloat-killed-my-voice-3ekd</guid>
      <description>&lt;p&gt;I measured the system prompt from my old setup last week. It came to &lt;strong&gt;224,833 characters, roughly 56,000 tokens, on every single call.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ninety percent of that was one block of accumulated rules about how the output should sound.&lt;/p&gt;

&lt;p&gt;I did not set out to build that. Nobody does. It grew one reasonable decision at a time, and by the end it was actively making the output worse, which I spent months blaming on the model.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(That prompt-engineering path is no longer what I run in production. It survives in my codebase as a rollback target, which is exactly why I could still assemble it and measure it.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How a prompt gets to 56,000 tokens
&lt;/h2&gt;

&lt;p&gt;The system generates short-form written content in a specific voice. I started the way most people do: a good frontier model, a carefully written prompt, and iteration.&lt;/p&gt;

&lt;p&gt;The loop that got me here is one every prompt engineer will recognise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Output comes back with a cliché I hate.&lt;/li&gt;
&lt;li&gt;Add a line to the prompt banning that cliché.&lt;/li&gt;
&lt;li&gt;Output improves. Genuinely, for a while.&lt;/li&gt;
&lt;li&gt;New failure appears. Add another rule.&lt;/li&gt;
&lt;li&gt;Repeat for six months.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each individual addition was justified. Each one was a real observed failure with an obvious textual fix. The categories that accumulated:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Banned phrases&lt;/strong&gt;, specific clichés that kept reappearing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Banned structures&lt;/strong&gt;, sentence shapes that read as machine-written&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Opener bans&lt;/strong&gt;, a list of ways it was not allowed to start&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Register rules&lt;/strong&gt;, tone and length and formality per content type&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anti-repetition context&lt;/strong&gt;, the openers and closers used in recent output, injected so it wouldn't repeat itself&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worked examples&lt;/strong&gt;, retrieved samples showing the target voice&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Meta-rules&lt;/strong&gt;, instructions about which of the above took priority when they conflicted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is the tell. &lt;strong&gt;When your prompt needs rules about how to resolve conflicts between its own rules, the prompt has become a program, and nobody is testing it.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Then came the validators
&lt;/h2&gt;

&lt;p&gt;Rules in a prompt are requests, not guarantees. So when a rule failed to hold, I did the obvious thing and enforced it in code afterwards.&lt;/p&gt;

&lt;p&gt;That grew into a stack of post-generation checks: regex to catch a banned sentence shape, a stripper for a punctuation habit, a rejection pass for output that restated its input, a vocabulary blocklist, a fallback for when everything got rejected. At one point I shipped seven of these as a single bundle.&lt;/p&gt;

&lt;p&gt;Each was a patch for something the prompt couldn't reliably enforce. And here's the part worth sitting with: &lt;strong&gt;I was compounding validators to compensate for a weakness in how the model was being steered.&lt;/strong&gt; Every new validator was evidence the prompt wasn't working, and my response was another validator.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom I misread
&lt;/h2&gt;

&lt;p&gt;Quality got worse as the rules accumulated.&lt;/p&gt;

&lt;p&gt;Not dramatically. Gradually. Output got flatter and more cautious, and it started sounding like something written by a committee avoiding mistakes rather than something with a point of view. Which, functionally, is exactly what it was.&lt;/p&gt;

&lt;p&gt;My diagnosis at the time was that the model wasn't good enough. I planned a move to a more expensive tier and put a cost estimate together.&lt;/p&gt;

&lt;p&gt;That would not have fixed anything. A stronger model given 56,000 tokens of conflicting constraints produces more expensive committee output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual diagnosis
&lt;/h2&gt;

&lt;p&gt;The rules were competing, with each other and with the task.&lt;/p&gt;

&lt;p&gt;A language model attends across the whole context. A prompt is not a config file where each line executes independently. It is context the model weighs all at once. Two hundred lines of prohibitions against a two-line description of the actual job means the overwhelming signal is &lt;em&gt;avoid things&lt;/em&gt;. The model optimises for whatever you have given it the most evidence you care about, and I had spent six months providing evidence that what I cared about was not breaking rules.&lt;/p&gt;

&lt;p&gt;That produces output which breaks no rules and says nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worse, it doesn't converge.&lt;/strong&gt; Adding a rule to fix flatness makes the ratio worse. The tool I was using to fix the problem was the thing causing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why prompting couldn't solve this one
&lt;/h2&gt;

&lt;p&gt;Here's the part I had underweighted for months.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can instruct a model toward a style. You cannot make a style native to it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prompting is instruction. It operates on top of what the model already is. Ask for a voice and you get an impression of that voice, held in place by the instruction, degrading the moment the instruction competes with anything else. The rules were load-bearing scaffolding. Remove them and the voice collapsed. Keep them and they crowded out the task.&lt;/p&gt;

&lt;p&gt;That is the signature of using the wrong technique. If a behaviour has to be re-specified in full on every single call, it does not belong in the prompt. It belongs in the weights.&lt;/p&gt;

&lt;h2&gt;
  
  
  What replaced it
&lt;/h2&gt;

&lt;p&gt;I fine-tuned a model on the voice instead. Later articles in this series cover the how, the cost and the evaluation discipline, but the number relevant here is what happened to the prompt.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Assembled system prompt&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prompt-engineering&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;224,833 chars, roughly 56,000 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fine-tuned model&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6,805 chars, roughly 1,700 tokens&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Difference&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;About 33x smaller&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You can measure your own the same way. Assemble the prompt exactly as your production path does and count it, rather than eyeballing the template file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_system_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# your real assembly path
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chars |&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;approx tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Break it down by section too. Mine was 90% one block, which is what told me the problem was concentrated rather than spread evenly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="n"&gt;sections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;={40,}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(((&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sections&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The voice rules didn't move into a shorter prompt. They moved into the model. What is left in the prompt is what a prompt is actually good at: what to write about right now, how long, which mode. Instruction, not identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lesson I'd give my past self
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Strip before you add.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When output is wrong, the reflex is to append a rule, because appending is easy and feels like progress. But every rule is a permanent tax, paid on every call, forever, competing with every other rule, and never reviewed. I never once removed a rule. I only ever added.&lt;/p&gt;

&lt;p&gt;Three questions I now ask before adding anything to a prompt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is this instruction, or identity?&lt;/strong&gt; Instruction belongs in the prompt. Identity, meaning a voice or a consistent style or a way of reasoning that must hold every time, belongs in the weights.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is this knowledge?&lt;/strong&gt; Facts the model needs but cannot be expected to hold belong in retrieval, not permanently in the prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What am I removing to make room?&lt;/strong&gt; If the answer is nothing, you are not editing. You are accreting.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The rough division that came out of it, and that I would now design around from day one:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fine-tuning owns identity. Prompting owns instruction. RAG owns knowledge.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Put a job in the wrong one and you will spend months adding rules that make it worse, which is a fairly expensive way to learn where the boundaries are.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>promptengineering</category>
    </item>
    <item>
      <title>0.3% In, 36% Out: Your Fine-Tuned Model Is Copying Your Prompt Examples</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Thu, 13 Aug 2026 17:08:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/03-in-36-out-your-fine-tuned-model-is-copying-your-prompt-examples-1f21</link>
      <guid>https://dev.to/aws-builders/03-in-36-out-your-fine-tuned-model-is-copying-your-prompt-examples-1f21</guid>
      <description>&lt;p&gt;I run a fine-tuned Llama 3.3 70B on Amazon Bedrock. It generates short first-person narrative posts: a setup, a few lines of story, and a closing line that lands the point.&lt;/p&gt;

&lt;p&gt;Last week I noticed the closing lines had collapsed into a single template. Not similar in spirit, but literally the same grammatical shape, over and over:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;and that's how [someone] [learns/teaches] [something].&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Roughly a third of everything the model produced ended that way. The obvious diagnosis was overfitting: the training data must be saturated with that pattern, so the model learned it as &lt;em&gt;the&lt;/em&gt; way to end a story. The obvious fix was equally clear. Scrub the training set, retrain, redeploy.&lt;/p&gt;

&lt;p&gt;In my setup that runs to about $30 of training compute and a five-hour training job, plus the evaluation pass afterwards before I'd know whether it had worked. Not ruinous, but not free either, and this would have been the third retrain cycle.&lt;/p&gt;

&lt;p&gt;Before spending it, I did something I should have done first. I counted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one: count the pattern in your training data
&lt;/h2&gt;

&lt;p&gt;My training file is JSONL, one example per line. The check took about ten seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"that's how"&lt;/span&gt; train_v6_2.jsonl | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
&lt;span class="c"&gt;# 5&lt;/span&gt;

&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; train_v6_2.jsonl
&lt;span class="c"&gt;# 1610&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Five occurrences in 1,610 training examples. 0.3%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Then I counted the same pattern in what the model was actually producing. Every generated post gets persisted, so this was one SQL query over the last 25 rows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;right&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ending&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;generated_stories&lt;/span&gt;
&lt;span class="k"&gt;order&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;desc&lt;/span&gt;
&lt;span class="k"&gt;limit&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Nine of twenty-five. 36%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Those two numbers are irreconcilable. Training data at 0.3% cannot produce output at 36%. Fine-tuning shifts a model's distribution, but it does not amplify a pattern by a factor of a hundred. Whatever was driving this, it was not the weights.&lt;/p&gt;

&lt;p&gt;Which meant a retrain would have changed nothing, and I'd have concluded my fine-tune was broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step two: read your own prompt
&lt;/h2&gt;

&lt;p&gt;The generation prompt included a line I had written months earlier and never revisited. Structurally it looked like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;THE CLOSER: lead with a thesis line that names what the story proves, e.g. &lt;em&gt;"and that's how [X] teaches [Y] to expect [Z]."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There it is. I had handed the model one complete, well-formed example of exactly the thing I wanted, and it did the most reasonable thing available to it. It copied the example, template and all.&lt;/p&gt;

&lt;p&gt;This is not the model misbehaving. Give a language model a single concrete instance of a pattern and ask it to produce that pattern, and the example becomes the strongest signal in the context window. It will out-compete a 0.3% tendency in the weights every time. I had effectively hardcoded my output and then blamed the training data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step three: fix the injection layer, not the model
&lt;/h2&gt;

&lt;p&gt;The fix cost nothing and shipped in an afternoon.&lt;/p&gt;

&lt;p&gt;Instead of one hardcoded example, I built a small pool of &lt;strong&gt;seven&lt;/strong&gt; closing lines. The key property is that they are &lt;strong&gt;structurally dissimilar&lt;/strong&gt;: a flat verdict, a two-beat reveal, a consequence line, a prescriptive line, an ironic callback. Critically, &lt;strong&gt;none of them uses the shape that was being copied.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My production lines come from a private archive, so here is an illustrative pool in a neutral domain that shows the same variety:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_CLOSER_EXEMPLARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The process was the problem.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nobody had tested it. Everybody had approved it.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;We saved an hour that afternoon and spent the next year paying for it.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write the runbook before you need it. You will not be calm enough later.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;So much for the quick fix.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;We called it a deadline for four months before anyone admitted it was a guess.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What would you have checked first?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;examples&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_CLOSER_EXEMPLARS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three are sampled at random per call, plus one explicit instruction: study the function these serve, then write a new one for this story, and do not reuse their wording.&lt;/p&gt;

&lt;p&gt;The reasoning is that &lt;strong&gt;one example teaches a template, while several dissimilar examples teach a function.&lt;/strong&gt; If every example looks different but they all achieve the same effect, the only thing left to imitate is the effect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;Measured on live inference against the same deployed model, same theme, no retraining:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Template rate&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before the fix&lt;/td&gt;
&lt;td&gt;36% (9 of 25)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After the fix&lt;/td&gt;
&lt;td&gt;8% (1 of 12)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sample sizes are small and I'd want more data before treating 8% as precise. But the direction is unambiguous, and it cost one prompt edit rather than a retrain cycle.&lt;/p&gt;

&lt;p&gt;The remaining 8% is, I think, genuinely the weights. That 0.3% is still in there, and a prompt fix can't reach it. That residue is a legitimate item for the next training-data scrub. The difference is that it's now a small, well-understood cleanup rather than a retrain I'd have run on a wrong diagnosis.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalizable part
&lt;/h2&gt;

&lt;p&gt;When a fine-tuned model produces something repetitive, there are three possible causes and they have very different price tags:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The prompt is handing it the pattern.&lt;/strong&gt; Free to fix. &lt;em&gt;Check this first.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The training data is saturated with the pattern.&lt;/strong&gt; Needs a scrub and a retrain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The base model has a strong prior.&lt;/strong&gt; Needs data specifically counteracting it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The diagnostic that separates them is one line of &lt;code&gt;grep&lt;/code&gt; and one SQL query. &lt;strong&gt;Compare the rate in your training data to the rate in your output.&lt;/strong&gt; If the output rate is dramatically higher, the cause sits in your prompt or your retrieval layer, not your weights. If the two rates are similar, it really is a data problem and a retrain is justified.&lt;/p&gt;

&lt;p&gt;Two habits came out of this that I'd suggest to anyone running a fine-tune on Bedrock:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Persist your generations.&lt;/strong&gt; I could only run the "after" comparison because every output was already in a table with a timestamp. Without that history there is no measurement, only impressions, and impressions are what sent me toward a retrain in the first place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat prompt examples as training data.&lt;/strong&gt; They are, functionally. Anything concrete you put in a prompt as an illustration will get copied, so either give none, or give enough variety that only the underlying function is imitable. One example is the worst of both worlds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The uncomfortable version of this lesson: the model was fine. I nearly spent money and two days proving that the hard way, and the evidence that it was fine was sitting in a file I could have counted at any point.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>machinelearning</category>
      <category>ai</category>
      <category>llm</category>
    </item>
    <item>
      <title>My 9-Month Journey to 9 Cloud Certifications: Juggling Work, Deadlines, Projects, and a New Baby</title>
      <dc:creator>Chidozie Uzoegwu</dc:creator>
      <pubDate>Fri, 10 Jan 2025 14:13:00 +0000</pubDate>
      <link>https://dev.to/chidozie/my-9-month-journey-to-9-cloud-certifications-juggling-work-deadlines-projects-and-a-new-baby-a47</link>
      <guid>https://dev.to/chidozie/my-9-month-journey-to-9-cloud-certifications-juggling-work-deadlines-projects-and-a-new-baby-a47</guid>
      <description>&lt;p&gt;Certifications earned in chronological order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS Certified Cloud Practitioner (CLF-C01)&lt;/li&gt;
&lt;li&gt;Microsoft Azure Fundamentals (AZ-900)&lt;/li&gt;
&lt;li&gt;Oracle Foundations Associate (OFA)&lt;/li&gt;
&lt;li&gt;AWS Solutions Architect Associate (SAA)&lt;/li&gt;
&lt;li&gt;AWS Developer Associate (DVA-C02)&lt;/li&gt;
&lt;li&gt;AWS Data Engineer Associate (DEA-C01)&lt;/li&gt;
&lt;li&gt;AWS Certified AI Practitioner (AIF-C01)&lt;/li&gt;
&lt;li&gt;AWS SysOps Administrator Associate (SOA-C02)&lt;/li&gt;
&lt;li&gt;AWS Machine Learning Engineer Associate (MLA-C01)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How It All&amp;nbsp;Started&lt;/strong&gt;&lt;br&gt;
How did I even end up with 9 certifications when my initial target was just to focus on AWS Solution Architect Associate (SAA), get certified, build some projects, and rest? Oh boy, I was mesmerized and fell in love with cloud knowledge when I started! Solution Architect (SA) sounded interesting, and the concept of building solutions for products, brands, and companies using cloud infrastructure was exhilarating. To be frank, Solution Architect keeps coming up randomly as a highly sought-after skill set, and the job market needs people with such expertise. I'd just be watching random interviews where the presenter would ask, "What do you do for a living?" and the correspondent would say, "I'm a Solution Architect. I do this and that for companies…" The next day, I'd be skimming through LinkedIn or X or other social platforms, and there again, I'd see discussions and blog posts about Solution Architects. Then I'd say to myself, "This is the sign - SA is calling me!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First Encounter with Cloud Computing&lt;/strong&gt;&lt;br&gt;
Let's retrace to the very beginning. My first encounter with the cloud was in one of my course modules during my Master of Data Science degree program called "Big Data and Cloud Computing." It amazed me that big data, programming, building solutions, storage, and many other activities could be done seamlessly on the cloud without needing to piece everything together - every service you need is already available on the platform like a one-size-fits-all solution!&lt;br&gt;
The idea fascinated me. I wouldn't have to store data on a hard drive, manually upload or import it into my programming environment, worry about losing data entirely, or deal with different subscriptions and platforms to achieve a solution. The cloud could provide it all as Infrastructure as a Service (IaaS), Platform as a Service (PaaS), and Software as a Service (SaaS) through a variety of services such as compute, storage, databases, machine learning, networking, analytics, and more. "Voilà," I said to myself, "Cloud is the way!"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Immigration Journey and EB-2 NIW Motivation&lt;/strong&gt;&lt;br&gt;
Procrastination set in at first as I explored other paths - whether to pursue a Ph.D. immediately or get a job as a Data Scientist. As an immigrant in the UK with an expiring student visa, the visa challenges of finding skilled-worker sponsorship loomed large. I had just started a new family too, and the urge to move to countries where I could have more stability, along with other concerns I won't burden the readers with, occupied my mind rather than getting cloud certified - until early 2024.&lt;br&gt;
In January, a Facebook friend recently shared his success story of obtaining USA EB-1 approval for a green card - a visa category for individuals with extraordinary abilities, outstanding professors, or multinational executives. This meant he would become a permanent resident in the USA while living in the UK on his previously approved Global Talent Visa. I reached out to him and discovered that I qualified for the EB-2 NIW (National Interest Waiver), a category for individuals with advanced degrees or exceptional abilities whose work benefits the U.S. However, I needed to demonstrate how "well-positioned" I was to advance my proposed endeavors. That sparked my motivation to pursue certifications, as they could serve as supporting documents to prove my exceptional abilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting with the Basics: Cloud Practitioner&lt;/strong&gt;&lt;br&gt;
Instead of jumping straight into the SAA certification, I needed to understand everything from scratch. Unlike Microsoft's Azure platform, which I had experienced in my Big Data and Cloud Computing module, I learned that AWS was the largest cloud provider with the biggest market share, so I focused on AWS. I started with the fundamentals through the AWS Certified Cloud Practitioner Certification (CLF-C02) from freeCodeCamp.org with instructor Andrew Brown on YouTube. The 14-hour video was comprehensive, but I couldn't follow it structurally as everything was mixed without sections, topics, and structure. So, I opted to subscribe to ExamPro, a platform developed by Andrew Brown, where I could find more structure in my learning and explore the practice exam questions - which are crucial for passing the main exam.&lt;br&gt;
My next decision came upon entering the ExamPro platform. I discovered a course bundle with basic cloud foundation courses from top providers called "Cross-Cloud Fundamentals Package": AWS Certified Cloud Practitioner, Microsoft Azure Fundamentals, and Oracle Foundations Associate. I thought to myself, why not subscribe to this package? After all, it's not a bad idea to learn about diverse cloud concepts from the basics. I went for it at $69 USD and instantly became a serious student.&lt;br&gt;
The Beast Mode&amp;nbsp;Phase&lt;br&gt;
Remember, during this period I was preparing my EB-2 NIW petition and gathering other documents for my application. I needed to speed up with my certifications to include as supporting documents, so my energy and "ginger" was on nitro! I was in beast mode and hyper-determined to succeed. I studied rigorously and mustered the wherewithal to meet my targets. I studied on my way to and from work, in the metro, during my free periods and breaks at work. At home, it was back-to-back studying if I was awake. Scratch that - I was waking up specifically to study, instead!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Foundation Certifications Success&lt;/strong&gt;&lt;br&gt;
In less than a month, I took and passed the AWS Certified Cloud Practitioner (CLF-C01) exam. Yay! My approach was simple: I covered all topics from Andrew Brown's course module and completed the hands-on practicals, but more importantly, I practiced the sample exam questions extensively, which prepared me for the real exam. Learning the course material alone wasn't enough - I needed to familiarize myself with the AWS exam structure, which played a major role in my success.&lt;br&gt;
Immediately after passing the CLF-C01 exam, I gained momentum like a toddler hungry for its mother's milk and swung into action toward Microsoft Azure Fundamentals (AZ-900). I realized that I had already studied the CLF-C01 material extensively, which largely overlapped with the concepts in AZ-900, with only minor differences in terminology. As a result, I only needed to focus on practicing the AZ-900 exam questions. I took the AZ-900 and passed. Yay! The same happened with Oracle Foundations Associate (OFA). Yay! The OFA was a free exam, unlike the CLF-C01 and AZ-900 that cost about $100 USD each.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbuiuv62351biw8zuuyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdbuiuv62351biw8zuuyn.png" alt=" " width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tackling the Solution Architect Associate&lt;/strong&gt;&lt;br&gt;
Next came the certification that started it all - the SAA. I began researching study materials, and everywhere I turned, I encountered "Stephane Maarek" and success stories from those who had taken his Udemy course. This led me to sign up for Udemy and purchase my first course: "Ultimate AWS Certified Solutions Architect Associate" by Stephane Maarek. It came with some practice questions, but for deeper preparation, I also bought the SAA practice exam by Abhishek Singh and Stephane Maarek, which contained six full practice exams. This gave me plenty of opportunities to familiarize myself with the material and prepare for the exam. Both courses cost just over £28, as I bought them during a Udemy promotional sale. Always watch for those promos!&lt;br&gt;
I prepared intensively for less than a month as my EB-2 NIW petition deadline approached. I passed! Yay! The exam usually costs $150 USD, but since I had passed the CLF-C01, I received a 50% discount on my next qualified exam. So, I paid just $75 USD for the SAA.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Developer Journey and Life&amp;nbsp;Changes&lt;/strong&gt;&lt;br&gt;
Upon passing the SAA, wild thoughts began to swirl in my mind: "Perhaps I can go for the AWS Developer certification and then the AWS Certified SysOps Administrator." Another voice chimed in, encouraging, "Why not complete all the associate-level AWS certifications and become more grounded in it?" Yet another joined, cheering me on, "Besides, you'll get half-price for the next exam since you passed the last one!"&lt;br&gt;
I gave in because, truthfully, I had become magnetized by the endless possibilities of cloud knowledge and, like Oliver Twist, yearned for more. I recalled Stephane Maarek's advice from his FAQ about "In which order should you learn?": "Start with AWS Certified Solutions Architect Associate, then move on to AWS Certified Developer Associate and then AWS Certified SysOps Administrator. Afterwards, you can either do AWS Certified Solutions Architect Professional or AWS Certified DevOps Professional, or a specialty certification of your choosing."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Developer Certification Challenge&lt;/strong&gt;&lt;br&gt;
So, I continued with AWS Developer (DVA-C02) certification. I purchased both the course and practice exams from Stephane Maarek at £28 each (promotional price - regular price over £60). DVA-C02 proved challenging, possibly due to its different approach and because I had a lot going on as I neared the final submission of my EB-2 NIW application. While preparing for the DVA-C02 certification, I was also preparing to submit my EB-2 application, and in three weeks, I took the DVA-C02 exam and failed. Nay! I was sad but not broken - I get motivated by failure; it sparks me as if I have something to prove. I can be stubborn like that!&lt;br&gt;
The sadness was short-lived, though, as my EB-2 NIW was approved in May. Success! YAY! The next day, I welcomed my baby into this world. YAY! so I took 4 weeks off to give my baby all the attentions needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building Real-World Experience: The Projects&amp;nbsp;Phase&lt;/strong&gt;&lt;br&gt;
After taking a break, I returned to my studies as if I'd never left. I went back to the drawing board and realized I needed more hands-on experience to build up my profile and portfolio with projects. I began exploring both free and paid hands-on projects.&lt;br&gt;
I started with free projects on YouTube:&lt;br&gt;
From Tech with Lucy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launched a website on Amazon S3&lt;/li&gt;
&lt;li&gt;Visualized data using Amazon QuickSight
From Tiny Technical Tutorials:&lt;/li&gt;
&lt;li&gt;Architected and built an end-to-end AWS web application from scratch&lt;/li&gt;
&lt;li&gt;Built a full end-to-end web app with 7 AWS services&lt;/li&gt;
&lt;li&gt;Created a free static website with AWS Amplify and Amazon S3&lt;/li&gt;
&lt;li&gt;Built a React app with Amplify (Gen 1), Cognito, and CI/CD with GitHub
Then came the paid projects:
From Tech with Lucy - AWS Cloud Projects ($63 USD):&lt;/li&gt;
&lt;li&gt;Built a daily task scheduler using Amazon PartyRock&lt;/li&gt;
&lt;li&gt;Created an image labels generator using Amazon Rekognition&lt;/li&gt;
&lt;li&gt;Developed a text narrator using Amazon Polly&lt;/li&gt;
&lt;li&gt;Built a language translation bot using Amazon Lex&lt;/li&gt;
&lt;li&gt;Deployed a bucket list tracker application on AWS Amplify
From Azeez Salu through aosnote.com ($49.95 USD each):&lt;/li&gt;
&lt;li&gt;Host a Dynamic Web App on AWS&lt;/li&gt;
&lt;li&gt;Deploy a Dynamic Website on AWS with Terraform
To ensure success on my next DVA-C02 attempt, I purchased another course by Jon Bonso. This provided additional learning opportunities, and combined with my hands-on project experience, I passed the exam in June after extra two weeks of study. Yay!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Data Engineering Challenge&lt;/strong&gt;&lt;br&gt;
By now, my mission had expanded to completing all associate-level certifications. I wanted to be versatile and not limited to just one area of cloud computing. I aimed to become a full-fledged cloud engineer with comprehensive knowledge. When AWS launched the Data Engineer certification (DEA-C01), it was a perfect fit since data is my area of expertise. I made this my next target before tackling the most challenging one - AWS Certified SysOps Administrator Associate.&lt;br&gt;
I purchased course materials from Frank Kane and Stephane Maarek, along with practice exams from Thomas Hass, both at the promotional price of £28. While studying, I attended the AWS Summit in London, where I met AWS partners and employees and gained first-hand experience with various AWS projects and services. This major event provided great insights into AWS. However, despite my preparation, I failed the DEA-C01 exam. NAY!&lt;br&gt;
What went wrong? Data Engineer was complex, especially as a new certification with limited study materials available. I had approached it too quickly without thoroughly understanding the core concepts of data engineering, which focuses on moving and processing data. Many services appeared similar but differed in operation and approach. I needed to return to basics and take more time to understand the material properly.&lt;br&gt;
For me, as I've mentioned before, failure doesn't weaken or depress me - it makes me more competitive and determined to win. I enjoy the challenge of overcoming failure. So, I purchased additional practice exams for AWS Certified Data Engineer from Abhishek Singh and Stephane Maarek, practiced for two more weeks, and passed! Yay!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The SysOps Administrator Battle&lt;/strong&gt;&lt;br&gt;
Next came the formidable AWS Certified SysOps Administrator Associate (SOA-C02). This proved to be the most challenging certification of all. Though I nearly gave up, I had to prove I could rise to the challenge. I started with Stephane Maarek's courses and practice exams but failed SOA-C02 twice! Nay! Nay!&lt;br&gt;
I purchased additional materials from Neal Davis and Jon Bonso and continued studying. During this time, AWS launched two certifications in my field of expertise: AWS Certified AI Practitioner (AIF-C01) and AWS Certified Machine Learning Engineer Associate (MLA-C01). This excited me for two reasons: it aligned with my specialty, and AI and ML were advancing rapidly. I decided to use these as a refreshing break from my battle with SOA-C02.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI and Machine Learning Certifications&lt;/strong&gt;&lt;br&gt;
AWS offered an "Early Adopter" badge to first-time earners of new certifications, so I tackled AIF-C01 first, purchasing course and practice exam materials from Stephane Maarek and Abhishek Singh. In just under three weeks, I passed - yay!&lt;br&gt;
Next, I returned to finish my showdown with SOA-C02, and this time, it didn't stand a chance. I delivered the knockout punch, mastering monitoring and reporting, high availability and resilience, deployment and provisioning, storage and data management, networking, security and compliance, and cost and performance optimization. Passing SOA-C02 felt like a massive milestone.&lt;br&gt;
Finally came MLA-C01. I purchased materials from Frank Kane and Stephane Maarek for both the course and practice exams, studied for less than a month, and passed - another yay! Although AIF-C01 and MLA-C01 were newly introduced, they turned out to be far less intimidating - my background in Data Science gave me a solid edge, and I happily breezed through both.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd031uunvsq5jd2wf1fj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd031uunvsq5jd2wf1fj9.png" alt=" " width="800" height="1163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cost Overview and Future&amp;nbsp;Plans&lt;/strong&gt;&lt;br&gt;
It's worth mentioning that I utilized various AWS exam discounts, including the "Associate Challenge" promotion. These discounts helped significantly with retaking certifications. Generally, foundational certifications cost $100 USD at regular rates, while Associate Certifications cost $150 USD. However, passing one certification earns you a 50% discount on your next exam.&lt;br&gt;
Speaking of next exams, I now have two bonus attempts available for professional certifications - either AWS Certified Solutions Architect Professional, AWS Certified DevOps Professional, or a specialty certification.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Looking Forward&lt;/strong&gt;&lt;br&gt;
Overall, I've thoroughly enjoyed the journey and experiences gained along the way. The ups and downs have shaped my current position as I continue learning and building. There's still so much to learn both in cloud computing and beyond. The world of Data Science is evolving at lightning speed, and I'm staying current with all the new technologies. What makes these experiences particularly exciting is the integration of AI, ML, and Data with Cloud technologies. I believe both are unstoppable, especially when combined. Currently, I'm working on a project using the latest AWS services - Sagemaker, Glue, Bedrock Agentic AI, Knowledge bases, Lambda Functions, Step Functions, CloudWatch, EC2, and others - as a solution for a brand. AWS continues to push boundaries, ensuring builders, users, and cloud engineers have all the resources they need as a platform-as-a-service. And that's truly the best part.&lt;br&gt;
As I keep learning and building, I'll be sharing my journey along the way! Bye for now and Keep Learning!&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
