OpenVINO #37435 — Why a NumPy Oracle Could Confirm the Same Wrong GELU_QUICK Formula

Real ggml captures, runtime-collected translator coverage, and why a passing self-derived reference was not independent evidence

The first two articles in this series examined two different contracts inside OpenVINO’s GGUF frontend.

Part 1 examined state ownership.

GGML cache-update semantics
        ↓
stateless SetRows boundary
        ↓
consumer chooses:

explicit cache I/O

or

OpenVINO-managed state

Part 2 examined tensor identity.

producer node

+

output port

+

axis meaning

+

layout

+

backend-visible topology

Part 3 examines a third boundary:

How does the frontend know
that its translation is actually equivalent to ggml?

A test can pass while both the implementation and the expected result are wrong.

Suppose the source implementation computes:

G(x)

A frontend author misunderstands it and implements:

F(x)

Then the same interpretation is used to write the test oracle:

expected = F(x)

The test compares:

translator output:
F(x)

test reference:
F(x)

and reports:

PASS

But the real source contract remains:

G(x)

The test has established only:

The translator agrees with the translator author’s interpretation.

It has not established:

The translator agrees with ggml.

OpenVINO PR #37435 describes this problem directly.

The historical example was:

GGML_UNARY_OP_GELU_QUICK

The operation had once been associated with the ordinary tanh-GELU formula rather than ggml’s actual quick-GELU definition:

x * sigmoid(1.702 * x)

The error was plausible.

It was also not being exercised by the operation test suite.

PR #37435 therefore added two independent protections:

1. Compare selected activation translators
   against output captured from real ggml.

2. Fail the full test suite
   when an operation is registered
   but no test fixture exercises it.

The central lesson is:

A reference implementation is useful only to the extent that its mistakes are independent from the implementation being tested.


The historical failure exposed two different test gaps

The GELU_QUICK case should be stated carefully.

The merged source documents that the operation had previously been registered with the wrong, tanh-GELU interpretation and remained unnoticed because nothing converted that operation in the test suite.

At the same time, PR #37435 explains a broader oracle problem:

A NumPy reimplementation
can only confirm the formula
the test author already guessed.

The two gaps are related but distinct.

Gap 1 — Missing execution coverage

Translator registered

but

no test constructs that operation

→ wrong code can remain invisible

Gap 2 — Correlated oracle

Translator formula

and

expected-value formula

come from the same interpretation

→ the same misconception can appear on both sides

PR #37435 closes both boundaries.

Coverage gate
→ makes missing translator tests visible

Captured ggml output
→ gives selected numerical tests an independent source

The current GELU_QUICK translator uses the correct contract

The merged translator implements:

scaled = 1.702 * x

result = x * sigmoid(scaled)

The source comments explicitly distinguish this from ordinary GGML GELU.

GGML_UNARY_OP_GELU
→ tanh GELU approximation

GGML_UNARY_OP_GELU_QUICK
→ x * sigmoid(1.702x)

They are not treated as interchangeable operations.

There is an important patch-scope distinction.

The unary_gelu.cpp file at the PR’s base revision already contained the corrected quick-GELU implementation, and the merged revision contains the same formula.

Therefore, PR #37435 should not be described as:

the commit that originally corrected
GELU_QUICK arithmetic

Its direct contribution here was:

make the historical class of error
harder to reintroduce unnoticed

through stronger references and coverage enforcement.


Two plausible activation formulas can remain close

The historical mistake was not a random expression.

Both operations are smooth nonlinear activations.

ordinary tanh GELU

and

x * sigmoid(1.702x)

can produce numerically similar results over part of the input domain.

Near zero or over a limited range, a superficial test may not make their distinction obvious.

But they are not the same function.

The new test comments record a difference of approximately:

2.2 × 10^-2

in the negative tail between the relevant formulations.

That difference is far larger than the tolerances used by the real-ggml comparison.

A sufficiently broad input range makes the semantic distinction observable.


A narrow input range can hide a wrong activation

Suppose a test uses only values such as:

0

0.1

0.5

1.0

Two smooth approximations may produce outputs close enough to satisfy a generous tolerance.

The test may report:

PASS

even when the operation identity is wrong.

A better activation test should include:

negative values

values near zero

positive values

regions where the approximations diverge most

The new captured reference uses a ramp covering approximately:

[-6, 6]

stored as a:

[4, 32]

array.

The negative tail is therefore part of the test rather than an unobserved corner.


What a self-derived oracle actually proves

Consider a translator written as:

def translated_gelu_quick(x):
    return guessed_formula(x)

Now consider its test:

expected = guessed_formula(x)

assert translated_gelu_quick(x) == expected

The test proves:

implementation
=
guessed formula

That may be useful for detecting:

  • wiring mistakes,

  • wrong constants in the implementation,

  • shape errors,

  • data-type mistakes,

  • or accidental later edits.

It does not independently prove:

guessed formula
=
ggml semantics

The implementation and oracle share one source of truth:

the author’s reading of ggml

If that reading is wrong, both can remain consistent.


Correlated failures can look like independent agreement

A test output normally appears to be two independent observations.

actual output

expected output

But independence depends on how those values were produced.

Stronger independence

Actual:
OpenVINO translation

Expected:
captured execution of real ggml

Weaker independence

Actual:
OpenVINO graph derived from author’s formula

Expected:
NumPy or C++ formula derived by the same author

The second comparison contains two implementations.

It may contain only one interpretation.

two code paths

but

one conceptual source

Rewriting source code is not the same as executing source code

A test author may inspect ggml and translate its kernel into NumPy.

That can be valuable.

It can also introduce errors while translating:

  • constants,

  • approximation variants,

  • operand order,

  • integer rounding,

  • lookup-table behavior,

  • broadcasting,

  • edge-case handling,

  • NaN behavior,

  • or quantized intermediate precision.

The expected result then describes:

the test author’s reconstructed ggml

rather than:

ggml itself

The distinction becomes especially important when the source implementation uses:

lookup tables

reduced-precision intermediates

specialized CPU kernels

approximation-specific constants

that are easy to simplify incorrectly.


PR #37435 added output captured from actual ggml

The new activation reference cases use committed .npy files.

For each selected operation, the test loads:

<input>_ggml_input.npy

<operation>_ggml_expected.npy

The tested operations are:

SILU

GELU

GELU_QUICK

The expected tensors were captured offline from real ggml execution.

At test time, OpenVINO:

builds the translated single-op graph

compiles it on CPU

runs the captured input

compares OpenVINO output
against captured ggml output

The test binary does not need to link ggml or llama.cpp during its normal build or execution.

The .npy files carry the captured reference instead.


The reference path is now source-independent at runtime

The comparison becomes:

Input captured for ggml
        ↓
OpenVINO translated operation
        ↓
OpenVINO output

versus:

Same input
        ↓
actual ggml kernel executed offline
        ↓
captured expected output

The test author still chooses:

  • the input domain,

  • which ggml build produced the reference,

  • the output format,

  • and the tolerance.

But the expected numerical values are no longer reconstructed by repeating the intended formula in the test.

That breaks the most dangerous correlation.


The captured output retains ggml’s own numerical behavior

The real ggml reference is not identical to an ideal mathematical formula in every bit.

The test source explains that ggml evaluates GELU and GELU_QUICK through an FP16 lookup-table path in the reference build.

That introduces small differences relative to an exact FP32 formula.

The tolerances are therefore operation-specific.

SILU:
1 × 10^-5

GELU:
2.5 × 10^-3

GELU_QUICK:
4 × 10^-3

The larger GELU tolerances are not arbitrary permission for a wrong activation.

They reflect the numerical behavior of the actual ggml execution path being used as the reference.

The historical quick-GELU versus tanh-GELU difference in the negative tail remains much larger than these bounds.


Bit-exact comparison would test a different contract

A zero-tolerance test would ask:

Does OpenVINO reproduce
every bit of the captured ggml implementation?

That may be too strong when:

  • OpenVINO evaluates in FP32,

  • ggml uses an FP16 lookup table,

  • the backend uses a different instruction sequence,

  • or mathematically equivalent expressions round differently.

The chosen comparison asks:

Does the translated operation remain within
the measured numerical envelope of ggml
while still distinguishing the wrong formula?

A useful tolerance should be:

large enough
→ to admit legitimate implementation-level rounding

small enough
→ to reject a semantic substitution

The hand-written formula tests were not removed

The test suite still contains parameterized scalar references written in C++.

For example, quick GELU is represented as:

x / (1 + exp(-1.702x))

Those tests remain useful.

They are:

  • readable,

  • fast,

  • easy to diagnose,

  • and capable of identifying simple translator mistakes.

The captured ggml tests add another evidence layer.

Hand-written formula test
→ readable local specification
Real-ggml capture
→ external semantic check

Neither layer needs to replace the other.

They answer different questions.


A human-readable oracle and an external oracle complement each other

A captured tensor alone can be difficult to inspect.

If one element differs, the test reveals:

index 47 mismatch

but may not immediately explain the formula.

The hand-written reference makes the intended mathematics visible.

quick GELU
=
x * sigmoid(1.702x)

The external reference determines whether that visible formula agrees with ggml.

Readable model

+

independent execution capture

→ stronger evidence

Captured references also have a lifecycle

A committed .npy file is not automatically timeless truth.

It belongs to:

  • a particular ggml source revision,

  • a particular build configuration,

  • a particular numerical kernel,

  • a particular input tensor,

  • and the capture method used at the time.

If ggml intentionally changes an operation later, the captured file may become stale.

A robust reference lifecycle should make clear:

which implementation generated it

which source revision was used

which input was supplied

how it was captured

why its tolerance was selected

PR #37435’s source comments establish that the files came from real ggml and explain the relevant arithmetic behavior.

The patch does not embed a full executable reference generator into the normal test suite.


Offline capture removes a dependency but also removes live comparison

The advantage is:

OpenVINO test build
→ does not require ggml

The tradeoff is:

the test does not automatically notice
that upstream ggml has changed

The reference remains fixed until someone regenerates it.

Therefore:

offline capture
→ strong independence from test formula

but

→ requires explicit provenance and update policy

The second protection is an operation-coverage gate

A correct oracle is useless for a translator that no test ever reaches.

PR #37435 added a gate comparing:

operations registered in get_supported_ops()

against:

operation types recorded by the test binary

The intended invariant is:

Every registered GGML translator
must have a corresponding test path

unless

an explicit, justified exemption exists.

The historical GELU_QUICK gap is named directly in the gate’s source comments as the motivating example.


The registered side comes from the real operation table

The gate does not maintain a second list such as:

std::set<std::string> operations_that_should_be_tested;

Instead, it iterates over:

ov::frontend::gguf::get_supported_ops()

That is the same table used by conversion to map GGML operation names to translator functions.

Conceptually:

registered =
keys(get_supported_ops())

A newly registered translator therefore enters the coverage comparison automatically.

The developer cannot update the production op table without changing the set inspected by the gate.


The tested side is collected while tests instantiate decoders

The single-operation test helper contains:

converted_op_types()

which returns a process-local set.

Every SingleOpDecoder constructor inserts its operation type:

converted_op_types().insert(m_op_type);

Therefore, when a test creates:

GGML_OP_TOP_K decoder

GGML_OP_ADD decoder

GGML_UNARY_OP_GELU_QUICK decoder

those names appear in the runtime record.

The gate later compares:

registered ops

minus

recorded test ops

minus

justified exemptions

Any remaining names are reported as missing.


Runtime collection is stronger than a manually maintained test list

A manual list can drift in two directions.

False coverage

Name appears in “tested ops” list

but

test was deleted or renamed

Missing enforcement

Translator added to op table

but

developer forgets to add name to coverage list

A runtime record follows the code paths that the test binary actually instantiates.

The record is a by-product of test construction rather than another list that must be edited in parallel.

Test creates decoder
→ operation records itself

That reduces one class of maintenance error.


The gate runs after the rest of the suite

The coverage record is populated gradually.

Running the check in an ordinary test body would make its result depend on test order.

Coverage test happens early

→ later translator tests have not run yet

→ false missing-op report

PR #37435 registers a global GTest environment.

Its:

TearDown()

runs after the normal test bodies have completed.

The sequence is:

Operation tests execute

→ runtime set accumulates op names

→ final environment TearDown runs

→ registered and recorded sets are compared

This gives the gate a complete view of the full test-binary run.


A filtered run intentionally disables the gate

Consider:

ov_gguf_frontend_tests \
  --gtest_filter=GGUFOps.TopK

Only one narrow subset runs.

The runtime record is necessarily incomplete.

If the final gate still compared that partial set with the full op table, it would report nearly every translator as missing.

The implementation therefore checks the active GTest filter.

The coverage assertion is enforced only when the filter is effectively:

*

or

*.*

Under a narrower filter, it logs that the coverage gate was skipped.

This is not a loophole accidentally left open.

It is required for targeted developer testing.


Full-suite coverage and filtered debugging serve different purposes

Filtered run

Goal:
debug one operation quickly

Coverage evidence:
partial by design

Full run

Goal:
validate the GGUF frontend suite

Coverage evidence:
complete operation-table comparison

A filtered green run therefore does not carry the same coverage claim as the reported full:

137/137

suite result.

The gate’s behavior makes that distinction explicit.


Exemptions require a source-level justification

The gate contains a:

coverage_exemptions()

set.

The comments state that an exemption must describe a real property of the operation.

An operation should not be exempted merely because writing its test is inconvenient.

A valid example could be:

alias operation

→ same translator function

→ same code path already tested under another name

At the merged revision, the exemption set contains no actual operation entries.

The comment notes that even GGML_OP_ADD1 has its own test because its broadcast shape differs.

The default policy is therefore:

registered translator

→ test required

rather than:

registered translator

→ test requested when convenient

Missing coverage fails outside an ordinary test body

The gate uses:

ADD_FAILURE()

from the global environment’s TearDown.

The source warns that such a failure can appear separately from ordinary test failures.

A log may report something resembling:

0 FAILED TESTS

while the overall process still exits unsuccessfully because the environment-level coverage assertion failed.

The error message is prefixed explicitly:

[GGUF op coverage gate]

so CI readers do not mistake it for an unexplained runner problem.


The failure message identifies every missing operation

The gate builds a sorted list of missing operation names.

Its failure message instructs the developer to:

add a case to test_ops.cpp

or

add a weight-leaf test to test_weights.cpp

If an operation genuinely cannot be isolated, the developer must add it to the exemption set with a reason.

The failure is therefore actionable.

It does not report only:

coverage mismatch

It reports:

which translator names are missing

and:

where the corresponding test should normally be added

The gate also tests itself

A guard can fail silently if its own wiring breaks.

PR #37435 adds two ordinary tests around the coverage mechanism.

Runtime record must be populated

The test constructs a decoder for:

GGML_OP_ADD

and verifies that the operation appears in:

converted_op_types()

Operation table must be non-degenerate

The test requires:

get_supported_ops().size() > 50

Without this check, an accidental empty operation table could make the coverage comparison pass vacuously.

registered set:
empty

recorded set:
anything

missing:
empty

→ false PASS

The two tests protect both sides of the set comparison.


There is an important limitation in what the gate records

The coverage comments describe the runtime set as operations that a test has converted.

The actual insertion occurs in:

SingleOpDecoder::SingleOpDecoder(...)

before:

FrontEnd::convert()

model compilation

or

inference

The sequence is:

Construct decoder
        ↓
record operation name
        ↓
possibly convert later
        ↓
possibly compile later
        ↓
possibly execute later

This means the gate directly proves:

At least one test path instantiated a SingleOpDecoder for this registered operation.

It does not, by itself, prove:

That test successfully converted, compiled, and executed the operation.

The distinction is visible in the guard’s own wiring test, which calls:

.decoder()

to populate the record without converting the resulting model.


The gate is stronger than a manual list but weaker than an execution receipt

A translator could theoretically be counted when a test only performs:

construct decoder

then stop

The gate would see the operation name.

It would not know that:

conversion never ran

or:

inference was never validated

In the existing suite, most SingleOpBuilder tests proceed through:

build

compile

infer

compare

But that stronger behavior comes from the individual tests.

It is not encoded in the coverage set itself.


Coverage has several possible levels

A more complete model would distinguish:

Level 1:
fixture constructed
Level 2:
frontend conversion completed
Level 3:
OpenVINO model validated
Level 4:
backend compilation completed
Level 5:
inference executed
Level 6:
output compared against an independent reference

PR #37435’s coverage gate is primarily a Level 1 registration guard coupled with ordinary tests that often provide the later levels.

It should not be described as proving Level 6 for every translator.

A future implementation could record milestones after successful conversion or execution, but that is an architectural extension rather than functionality added by this patch.


“Test exists” and “semantic oracle exists” are different claims

An operation may have a test that checks only:

conversion does not throw

Another may execute inference against a hand-written formula.

Another may use real ggml output.

All three satisfy some notion of coverage.

Their evidence strength differs.

Test boundaryWhat it establishes
Decoder instantiatedOperation appears in test wiring
Conversion succeedsTranslator can produce a valid OpenVINO graph
Inference succeedsCompiled graph can execute on the selected backend
Hand-written expected values matchTranslator agrees with the local formula
Captured ggml values matchTranslator agrees with the captured source implementation within tolerance

The coverage gate makes the first boundary mandatory.

Individual operation tests determine how far beyond it the evidence goes.


Why the real-ggml cases are still special

The new full-table gate ensures that:

every translator name
has some test representation

It does not make every expected result independent.

Many operation tests still use C++ references written from the operation’s intended formula.

That is reasonable for simple operations.

The captured real-ggml references are stronger specifically because they protect cases where:

the operation name is familiar

the formula looks plausible

multiple approximations exist

and

the wrong approximation can remain numerically close

GELU and GELU_QUICK fit that category.


Not every operation requires a binary captured reference

For an operation such as:

Add

a local reference:

expected[i] = a[i] + b[i]

has little interpretive ambiguity.

For a more specialized operation involving:

  • quantization,

  • approximation kernels,

  • layout-dependent semantics,

  • tie-breaking,

  • or backend-specific numerical behavior,

a source-executed reference may be much more valuable.

The patch does not prescribe one oracle type for every translator.

It adds an independent layer where the historical risk justified it.


The dedicated CI component makes the gate operational

PR #37435 also added:

GGUF_FE

as a dedicated affected component.

The GGUF frontend test binary is registered in the test-coverage configuration, and the corresponding workflow can run when GGUF frontend files are changed.

This matters because a coverage gate is useful only when the full binary actually runs in the relevant CI path.

Source-level guard

+

component-level CI routing

→ enforceable repository contract

The PR reports:

ov_gguf_frontend_tests:
137 / 137

for its validation.


What 137/137 directly proves

For the reported full run, the evidence supports:

All 137 tests in that GGUF frontend binary passed.

The real-ggml activation comparisons passed
within their operation-specific tolerances.

The global operation-coverage check
did not report an untested registered operation.

The stateful and stateless transformation tests passed.

The translator and quantization tests included in the binary passed.

It is meaningful evidence for the merged frontend groundwork.


What 137/137 does not prove

The result does not establish that:

Every GGUF model architecture is supported.
Native .gguf file loading is merged.
Every registered translator has a real-ggml oracle.
Every op_case, dtype, rank, and dynamic-shape combination is covered.
Every plugin produces identical numerical behavior.
Every translator reaches model-level inference.
Every future ggml semantic change will automatically update the captured references.
Every filtered test run enforces the coverage gate.

The separate native .gguf graph builder remains outside merged PR #37435.

The reported suite validates the existing decoder-based frontend and the groundwork included in this patch.


A captured reference can be correct and still incomplete

The real-ggml activation files cover:

SILU

GELU

GELU_QUICK

over one selected input shape and range.

They do not cover:

  • every tensor rank,

  • every element type,

  • NaN or infinity behavior,

  • every hardware implementation,

  • every future ggml lookup table,

  • or every activation registered by the frontend.

The new reference answers a precise question:

Does the current translated operation agree with the captured real-ggml output for this input domain and tolerance?

That is stronger than a duplicated formula.

It is not universal proof.


The patch converted a historical mistake into two enforceable contracts

Before the new infrastructure, the failure pattern was:

Translator registered

No direct test constructs it

Wrong but plausible formula remains

No independent source execution checks it

After the patch:

Translator registered
        ↓
full-suite coverage gate requires a test fixture

and:

selected activation translator
        ↓
executed on captured input
        ↓
compared with actual ggml output

The two contracts reinforce each other.

Coverage gate
→ ensures the operation is not invisible
Independent oracle
→ ensures visibility does not merely repeat the same assumption

The operation table and test suite now form a live relationship

The operation table is no longer only production configuration.

During a full test run, it also defines the required coverage set.

Add translator to get_supported_ops()
        ↓
translator appears in coverage comparison
        ↓
full suite requires matching test record

This changes the cost of adding unsupported or untested functionality.

Before:

translator can be registered

test can be forgotten

suite may remain green

After:

translator registered

test absent

→ environment-level failure

The coverage contract follows the production registry.


The strongest improvement was not more test cases alone

PR #37435 certainly added tests.

The deeper improvement was changing how test evidence is related to implementation.

Before

Production op table

and

test list

could drift independently

After

Production op table
→ defines required operation set

Before

Expected activation output
→ could be reconstructed from the same interpretation

After

Expected activation output
→ captured from actual ggml execution

The patch reduced two forms of correlated error:

registry drift

and

oracle drift

The complete three-part structure

Part 1 — State ownership became a consumer choice

GGML cache update
        ↓
neutral SetRows representation
        ↓
default stateless lowering

or

consumer-selected GGUFMakeStateful

The decoder describes the model.

The transformation selects the runtime-state policy.


Part 2 — Tensor identity included port and layout

TopK node
+
output 1
→ indices
same node
+
implicit output 0
→ values

The article also examined token-axis preservation, static head information, layout-polymorphic reshapes, and backend matcher topology.


Part 3 — Test agreement needed an independent source

translator formula

+

test formula derived from same interpretation

→ correlated PASS

The patch added:

captured real-ggml activation outputs

+

registered-op coverage enforcement

The frontend is now better protected against both:

wrong operations that no test reaches

and

wrong interpretations that a self-written oracle repeats

The final lesson is about independence of evidence

A test suite can be large and still share one assumption with the code it tests.

Implementation:
formula F

Oracle:
formula F

Result:
PASS

The missing question is:

Who established that F is the source contract?

A stronger chain is:

Source implementation executes

→ output captured

→ translated graph executes independently

→ results compared

The coverage gate then adds:

Every registered translator
must at least enter the test system.

Coverage answers whether a code path is observed.
An independent oracle answers whether the observed path means the right thing.

Both are necessary.

A test cannot validate a translator it never reaches.

And reaching a translator does not prove correctness when the expected result repeats the same misunderstanding.


Previous articles

  • OpenVINO #37435 — Why GGUF Statefulness Became a Consumer Choice Instead of a Decoder Property

  • OpenVINO #37435 — Why Dropping an ov::Output Port Could Turn TopK Indices into Values


Related material


Patch status: Merged into OpenVINO master as commit 57070bcb
Historical test failure: GELU_QUICK had once been associated with tanh GELU and remained unnoticed because no test converted it
Current GELU_QUICK contract: x * sigmoid(1.702x)
Independent activation references: Captured output from real ggml for SILU, GELU, and GELU_QUICK
Reference input: [4, 32] ramp spanning approximately [-6, 6]
Recorded tolerances: 1e-5 for SILU, 2.5e-3 for GELU, and 4e-3 for GELU_QUICK
Coverage source: Registered operations from get_supported_ops()
Runtime coverage record: Operation names inserted when SingleOpDecoder instances are constructed
Coverage evaluation: Global GTest environment TearDown() after the full suite
Filtered-run behavior: Coverage gate is skipped under a narrowed --gtest_filter
Validation: ov_gguf_frontend_tests reported 137/137
Evidence limitation: The gate proves test-fixture presence more directly than successful inference, and only selected activation operations use real-ggml output as an independent oracle
Native file-builder status: Separate PR #37421 remains outside merged #37435

This is Part 3 and the final article in the OpenVINO GGUF frontend groundwork series.

Part 1 examined why KV-cache state became a consumer-selected transformation rather than a decoder property.

Part 2 examined why output-port identity, token-axis placement, static head dimensions, and matcher-visible topology had to survive frontend translation.

This final article examined why a self-derived oracle can repeat the same mistake as the translator, how real ggml output creates stronger numerical evidence, and how the operation-coverage gate prevents newly registered translators from remaining invisible to the full test suite.

#OpenVINO #GGUF #GGML #GELU #GELUQuick #Testing #ReferenceOracle #ModelFrontend #GraphCompiler #RegressionTesting #CodeAnalysis

Popular posts from this blog

AMD Is Trying to Turn Kernel Diversity Into One Software Asset

PyTorch #188031 — Why Did Forward Use 64-Bit Indexing While Backward Still Used uint32_t?

NVIDIA CUTLASS #3017 — Why load() and store() Mapped the Same Tile Coordinate to Different Addresses