implement f16 - #9
Conversation
The lexer enumerated sm_10 through sm_35, so every newer .target was a lexical error and ocelot aborted. That pinned callers to Kepler, which CUDA 12 no longer supports at all. Nothing reads the target string back: targetElement pushes it into statement.targets and ir/Module.cpp hardcodes "sm_21" for internally built modules. So one text-carrying token replaces the eight hardcoded ones and never needs updating for a new architecture.
PTX types bfi's pos and len operands .u32 regardless of whether the instruction is .b32 or .b64, but the parser types immediates from the instruction type, so `bfi.b64 d, a, b, 32, 32` yielded b64 immediates and was rejected. Operand 1 already exempts immediates; operands 3 and 4 did not. Operand 4 also tested b.type instead of c.type. eval_Bfi already reads both as operandAsU32 for .b32 and .b64, so only the validator disagreed. nvcc emits bfi.b64 from sm_50 on.
cvt to and from f16 threw "conversion not implemented", and add, mul, max, setp, mov and ex2 rejected f16 as an instruction type, so nothing targeting sm_53 or newer could use half at all. f32 represents every f16 exactly, so operandAsF32 widens an f16 operand and the existing f32 paths are reused unchanged: cvt gets f16 as a source by falling through to the f32 case, and setp likewise. Destinations narrow with floatToHalf. The exact result of an f16 add or mul also fits in f32 (11 bit significands, so a product needs 22 of the 24 available), so computing in f32 and rounding once to f16 is correctly rounded, not double rounded. ex2 is .approx in PTX, so the f32 approximation is in spec. halfToFloat and floatToHalf were checked against numpy: all 65536 half bit patterns widen exactly, and 531082 narrowing cases including every exact tie midpoint round identically, ties to even. mad and min still reject f16 since they are not implemented for it. Measured on tinygrad's emulated CI, test/backend with DEV=MOCK+CUDA:PTX: sm_35 stays 1738 pass 0 fail 453 skip, identical to master. sm_53 goes from 109 errors to 1738 pass 0 fail 443 skip, the 10 freed skips being the fp16 tests that could never run before.
|
Closing: this is incomplete and I found it after opening, so it does not meet the bar. nvrtc declares half registers The widening here keys off the operand type, so those reads are wrong. Worse, before this change the validator cleanly rejected It is unreachable at sm_35, since half is disabled below sm_53, and I verified no change there. But unreachable is not correct. Reopening once an #8 is independent of this and unaffected. |
Stacked on #8. I cannot set a fork branch as the base, so the diff also shows #8's two commits; the change here is the last one,
implement f16. Without #8.target sm_53does not parse, so none of this is reachable.cvtto and from f16 threwconversion not implemented, andadd,sub,mul,abs,neg,min,max,fma,set,setp,movandex2rejected f16 as an instruction type. So nothing targeting sm_53 or newer could use half, and for a CUDA 12 toolchain that is every target, since sm_35 is gone.f32 represents every f16 exactly, so
operandAsHalfwidens an operand and the existing f32 paths are reused:cvttakes f16 as a source by falling through to the f32 case,setandsetplikewise, and destinations narrow withfloatToHalf. The reuse is exact rather than merely convenient: half significands are 11 bits, so the exact product of two halves needs 22 of f32's 24 and the exact sum fits too, which makes computing in f32 and rounding once correctly rounded rather than double rounded.ex2is.approxin PTX, so the f32 approximation is in spec.Reading an operand cannot key off the declared type. tinygrad's PTX renderer declares halves
.f16; nvrtc declares them.b16and puts the op in inline asm:The half is in the low 16 bits either way. Immediates keep the f32 handling, because the parser types an immediate from the instruction and the lexer has no
0Hhalf literal, so a constant reaching an.f16instruction was written as a float and lives inimm_single; reading it as bits picks up the wrong member of the union.cvtneeded one more thing.relaxedConvertstores the mnemonic's type ina.relaxedTypeand leavesa.typeas the register declaration, sooperandAsF32has to honour either. Without that, half loads on the nvrtc path came back as denormals like2.15e-41, which is0x3C00sitting in a float.setalso accepts a.b16destination, since that is the register nvrtc writes the1.0h/0.0hinto.halfToFloatandfloatToHalfwere checked against numpy before being wired in: all 65536 half bit patterns widen to the exact f32 bit pattern, and 531082 narrowing cases including every exact tie midpoint round identically, ties to even.madstill rejects f16, since it is not implemented for it. Opening a gate without an implementation turns a clean abort into a wrong answer.Measured on tinygrad's emulated CI,
test/backend, on both emulator consumers,DEV=MOCK+CUDA:PTXandDEV=MOCK+NV:Zero failures everywhere, and sm_35 is unchanged on both cells, so this is a no-op at the current target. The freed skips at sm_53 are the fp16 tests, which have never been able to run: tinygrad disables half below sm_53, so a Kepler-reporting mock meant the NV emulator never tested fp16 at all. The nv cell at sm_53 runs on nvrtc 12.9 with matching cudart headers.
I closed the earlier version of this PR because it only handled tinygrad's PTX and silently returned wrong values on the nvrtc path. That is fixed and both paths are now verified.