Code Generation & RLHF10 min read·Human Consensus AI Team

RLHF for Code Generation Models: How to Build Training Data for Coding AI

The standard RLHF playbook — collect pairwise preferences, train a reward model, fine-tune with PPO — was designed for natural language tasks where "better" is entirely a matter of human judgment. Code is different. Code has an objective correctness layer (does it run? does it pass tests?) and a subjective quality layer (is it idiomatic? is it secure? is it maintainable?). Both layers require human judgment — just in different ways. If you're building or fine-tuning GitHub Copilot-style assistants, CodeLlama, DeepSeek-Coder, or Codestral, here's what that means for your annotation pipeline.

Why Code Generation RLHF Is Different from NLP RLHF

In natural language RLHF, preference is almost entirely subjective. Ask two expert annotators which response better explains a historical event — they might disagree on emphasis, depth, or framing, but there's no ground-truth correct answer to anchor the disagreement. The preference signal is genuinely the thing you're training toward.

Code has a different structure. There is an objective layer: does the function return the correct output? Does it pass the test suite? Does it handle edge cases without crashing? This layer can, in principle, be checked mechanically. But mechanical verification of correctness is not the same as evaluating code quality — and conflating the two is the most expensive mistake teams make when building code RLHF pipelines.

Consider GitHub Copilot or DeepSeek-Coder generating a Python function to parse JSON from an API response. A test suite might verify that the function returns the right data structure on a set of controlled inputs. It will not catch that the function uses a global variable that creates a race condition under concurrent requests, that it swallows all exceptions silently, or that it hardcodes a retry delay in a way that will cause production timeouts at scale. The code passes the tests and fails in production.

This is the fundamental tension in code RLHF: automated test suites address functional correctness, but they leave two of the three dimensions that determine code quality entirely unmeasured. Those dimensions require human judgment — specifically, the judgment of engineers who write production code in the language and domain the model is being trained on.

For context on why automated metrics generally fail as a training signal proxy, see LLM evaluation benchmarks vs. human evaluation. The code case makes that argument concrete.

The Three Dimensions of Code Quality Annotators Must Evaluate

Code preference annotation requires evaluating three distinct dimensions. Each has a different relationship to automated checking — and each requires a different annotator profile to evaluate reliably.

Dimension (a) — Functional Correctness

Does the code solve the stated problem? Does it handle edge cases — empty inputs, null values, boundary conditions, concurrent access? Does it produce the correct output for inputs not in the provided test suite? Automated tests catch the cases they were written for. Human annotators catch what the test suite missed: the off-by-one error that only surfaces at array boundaries, the assumption about character encoding that fails on non-ASCII input, the integer overflow that only triggers at production data volumes. Functional correctness is the most "objective" of the three dimensions — but it still requires an engineer who can reason about execution behavior beyond the test cases provided.

Dimension (b) — Code Quality

Is the code idiomatic for the language and ecosystem? Does it use appropriate abstractions — not too clever, not too verbose? Are naming conventions consistent and self-documenting? Is the logic structured for readability by future maintainers, or is it optimized in a way that obscures intent? This is the dimension where senior engineers will have legitimate disagreements — and where crowdworkers cannot contribute meaningfully. A crowdworker can tell you if code compiles. They cannot tell you whether a list comprehension is more Pythonic than a for-loop in a given context, or whether a TypeScript generic is appropriately typed or unnecessarily complex. Code quality annotation requires professional judgment. No automated metric captures it.

Dimension (c) — Security and Safety

Does the code introduce SQL injection vulnerabilities through string concatenation instead of parameterized queries? Does it expose buffer overflow risks via unsafe memory operations? Does it hardcode credentials, API keys, or secrets? Does it use deprecated or known-vulnerable dependencies? Does it implement authentication checks in a way that can be bypassed? Security evaluation requires an annotator who knows the attack surface — not just that a function accepts user input, but what an attacker can do with it. Test suites almost never include adversarial inputs designed to probe security boundaries. This dimension is invisible to automated evaluation and high-stakes to miss.

The critical insight: automated test suites catch dimension (a) for the specific inputs they test. They catch almost nothing in (b) and essentially nothing in (c). A code RLHF pipeline that relies on test pass rates as the preference signal is training a reward model on one-third of what determines code quality — and ignoring the two dimensions that are most dangerous to get wrong.

Why Automated Metrics Alone Don't Work for Code RLHF

HumanEval and MBPP are the standard code generation benchmarks. Both measure pass@k: given k generated solutions, what fraction solve the problem on the provided test suite? This is a useful signal for capability assessment. It is not a useful signal for RLHF preference training.

The problem is that a fixed test suite is not the same as "good code." Three concrete examples of code that passes all HumanEval tests and fails on real quality dimensions:

Race condition behind a passing test suite

A function that reads and increments a counter passes every test in a single-threaded test harness. In production, under concurrent requests, the read-modify-write cycle produces a race condition that corrupts the counter. HumanEval has no concurrent test cases. The function scores pass@1. The bug ships.

Correct but unmaintainable at scale

A regex that correctly parses the ten test inputs provided in the benchmark — but fails silently on production edge cases not in the test suite. Or a function that solves the problem through nested conditionals that work correctly but are impossible to modify without introducing regressions. Pass@k measures whether it works now, not whether it can be maintained as requirements change.

Passing tests, security hole

A SQL query builder that produces correct results for all test inputs while constructing queries via string interpolation rather than parameterized queries. Every test passes. Every production deployment with user-controlled input is vulnerable to SQL injection. No benchmark test suite probes for this.

The broader argument — that benchmark scores are a poor proxy for the quality signal you actually want to optimize — applies throughout AI evaluation. See LLM evaluation benchmarks vs. human evaluation for the full case. In code, the gap between "passes HumanEval" and "good production code" is wide enough to drive a security incident through.

Human Consensus AI connects you with senior engineers who evaluate code on all three dimensions — correctness, quality, and security.

Expert engineers, not crowdworkers. Domain-matched annotators for the language and ecosystem your model is being trained on.

View Products →

Who Should Annotate Code Training Data

The annotator quality problem is more acute in code RLHF than almost anywhere else in the RLHF pipeline. The reason is structural: evaluating code quality requires professional coding experience in the specific language and domain being annotated. A non-programmer cannot evaluate whether a function is idiomatic Python. A generalist developer cannot evaluate whether a Rust function has sound memory management. A backend engineer cannot reliably evaluate whether a React component uses appropriate patterns for the ecosystem.

The minimum bar: 2+ years of professional coding experience in the target language. The ideal: senior engineers who write production code in the language and domain the model is being trained on. If you're fine-tuning a model for ML engineering tasks, you want Python annotators who work on ML systems daily — not generalist Python developers. If you're training a model for systems programming, you want annotators who understand memory safety implications in context, not engineers whose primary language is TypeScript.

Annotator ProfileCorrectness κQuality κSecurity κ
Generalist crowdworkers0.410.280.19
Junior developers (1–2 yrs)0.580.390.31
Senior engineers (domain-matched)0.740.610.58
Senior engineers + calibration rounds0.810.680.65

Illustrative IAA ranges (Cohen's κ) for code preference annotation by annotator profile. κ < 0.40 is below the useful threshold for reward model training.

Note that even domain-matched senior engineers achieve κ ≈ 0.55–0.65 at best on style and quality dimensions — not because one annotator is wrong, but because senior engineers often have legitimate disagreements about idiomatic style. Two experienced Python engineers can reasonably disagree about whether a list comprehension or an explicit loop is more readable in a given context. This is an argument for calibration rounds (to surface and resolve systematic disagreements in the rubric) rather than for crowdworkers (who disagree for the wrong reasons: they don't understand the code well enough to have an informed opinion).

For the full case on why domain expertise determines annotation quality across RLHF tasks, see domain-expert annotators vs. crowdsourcing for AI training data.

What a High-Quality Code Preference Dataset Looks Like

The minimum viable structure for a code preference pair is more complex than a standard NLP preference pair. A standard preference pair needs: prompt + response A + response B + preference label. A code preference pair needs to capture the three evaluation dimensions separately, because a reward model trained on aggregate preference conflates correctness, quality, and security into a single score — making it impossible to know what the RM actually learned to optimize.

Minimum structure per preference pair:

  • Problem statement (task description + acceptance criteria)
  • Target language + relevant constraints (performance, memory, style guide)
  • Candidate A — the preferred solution
  • Candidate B — a realistic failure mode (not a straw man: code that compiles and works on simple cases but fails on the dimensions being evaluated)
  • Annotator judgment at the dimension level: correctness verdict + rationale, quality verdict + rationale, security verdict + rationale
  • Overall preference label with annotator ID for IAA computation

The InstructGPT approach of requiring annotators to write rationales — not just pick A or B — is especially important for code preference data. A rationale forces the annotator to articulate why one solution is preferred at each dimension. This serves two functions: it makes the annotation auditable (you can verify the rationale reflects genuine expert judgment) and it surfaces rubric gaps (when annotators struggle to articulate a rationale, the rubric is under-specified). See how to build a preference dataset for RLHF for the broader structure.

Language-specific considerations

Python — Quality dimension example

Candidate B (lower quality)

def get_user_scores(user_list):
    result = []
    for i in range(len(user_list)):
        u = user_list[i]
        score = 0
        for j in range(len(u["events"])):
            score = score + u["events"][j]["points"]
        result.append({"id": u["id"], "total": score})
    return result

Candidate A (preferred)

def get_user_scores(users: list[dict]) -> list[dict]:
    return [
        {"id": user["id"], "total": sum(e["points"] for e in user["events"])}
        for user in users
    ]

Annotator rationale (quality): Candidate A uses idiomatic Python — list comprehension, generator expression for sum, type hints. Candidate B uses index-based iteration (non-idiomatic), manual accumulation (unnecessary), and no type annotations. Both are functionally correct; quality dimension drives the preference.

Language-specific rubric considerations:

  • Python: PEP 8 compliance, type hints, generator expressions vs. list comprehensions by context, memory efficiency for large data structures
  • JavaScript/TypeScript: async/await patterns vs. raw Promise chains, type safety and generic appropriateness, bundle size awareness (tree-shaking, side-effect-free imports)
  • Systems languages (Rust, C++): memory safety (ownership, borrowing, lifetimes), allocation patterns, zero-cost abstraction usage, unsafe block justification

Each of these requires annotators who work in the language daily — not engineers who have passing familiarity with it.

Cold Start: How to Bootstrap a Code RLHF Dataset

The cold start problem in code RLHF is acute: you need preference pairs that accurately represent the quality dimension you want to optimize, but generating high-quality pairs requires either a capable base model (to generate plausible candidates) or expensive expert time (to write them from scratch). Two approaches address this with different cost and signal tradeoffs:

Approach (a) — Model-Generated Pairs with Expert Correction

Have the model generate two solutions to the same coding task. Have a senior engineer review both, select the preferred one (or rewrite it to a higher standard), and document the reasoning at each dimension level. This approach is cheaper per pair than full expert demonstration and scales well once the base model is capable of generating plausible attempts. The annotation task becomes evaluation and refinement rather than generation from scratch. Works best when the base model already has non-trivial code generation capability — at least 40–50% of generated pairs should have one solution clearly preferable to the other on at least one quality dimension.

Approach (b) — Expert Demonstration Traces

Have senior engineers write the gold-standard solution from scratch for each task, then generate a plausible-but-flawed alternative (either from the base model or by deliberately introducing a realistic failure mode). More expensive per pair — you're paying senior engineer rates for code authorship, not just annotation — but the signal quality is higher because the preferred solution reflects genuine expert judgment unconstrained by what the model can generate. The preferred solution is ground truth. Critical for cold start, when the base model may not be capable of generating a preferred solution worth selecting.

Practical recommendation: start with approach (b) for the first 500 preference pairs (cold start signal). Expert demonstration traces at this volume provide enough high-quality signal to train an initial reward model that can guide further data collection. Then shift to approach (a) at scale — model-generated pairs with expert correction are 3–5× cheaper per pair and maintain quality once the base model is capable enough to generate plausible candidates. The cold start investment in (b) makes (a) viable.

For the foundational RLHF dataset mechanics this builds on, see how to build an RLHF dataset from scratch. The code case applies the same principles with additional structure for the three quality dimensions.

Getting Started: Minimum Viable Code RLHF Dataset

Minimum viable code RLHF dataset for a production-quality code generation model:

01

1,000–2,000 preference pairs

In the target language and domain. Below 1,000 pairs, the reward model has insufficient signal to learn reliable quality judgments across all three dimensions.

02

κ ≥ 0.65 on functional correctness

Inter-annotator agreement on correctness dimension. Below this threshold, the reward model is learning noisy correctness signal — the most dangerous failure mode for a code generation assistant.

03

κ ≥ 0.55 on style/quality

Lower target than correctness, because legitimate expert disagreements on idiomatic style are expected. Achieving κ ≥ 0.55 requires calibration rounds to surface and resolve systematic rubric disagreements.

04

Domain-matched senior engineers

Annotators with 2+ years of professional experience in the target language and domain. Not generalist developers. Not crowdworkers. Domain match on both language and problem type.

05

Dimension-level rationales

Annotator judgment documented separately for correctness, quality, and security — with free-text rationale for each. Aggregate preference labels without dimension breakdown produce a reward model you can't audit or debug.

06

Calibration rounds before full collection

Run 50–100 pairs with your full annotator pool before the main collection. Compute IAA by dimension. Surface rubric gaps. Retrain annotators on disagreements before scaling data collection.

For guidance on reward model quality once you have the preference data, see how to evaluate RLHF reward models. The evaluation methodology applies to code reward models — with the additional consideration that reward hacking in code RLHF often manifests as length bias (longer code with more comments scores higher regardless of quality) or format exploitation (the RM learns to prefer code with docstrings regardless of whether the docstring is accurate).

Expert engineers, not crowdworkers — for code RLHF data you can trust

The Human Consensus AI Starter Pack provides expert-annotated preference pairs from domain experts using the same methodology described in this post: dimension-level evaluation, calibrated IAA, senior engineers with production experience in the target language.

Get the Expert Opinion Starter Pack — $49 →

Building a production-scale code RLHF pipeline? Enterprise programs include dedicated rubric design for your target language and domain, domain-expert annotator sourcing, calibration management, and ongoing IAA monitoring across all three code quality dimensions.

View Enterprise Bundle →